├── .gitattributes ├── .gitignore ├── Directory.Build.props ├── SDL.SignalR.OracleMessageBus.sln ├── ServerRun.testsettings ├── Tests └── SDL.SignalR.OracleMessageBus.Tests │ ├── DbOperationTest.cs │ ├── DbProviderFactoryAdapterTest.cs │ ├── ObservableDbOperationTest.cs │ ├── OracleDependencyManagerTest.cs │ ├── OracleInstallerTest.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ReceiverTest.cs │ ├── SDL.SignalR.OracleMessageBus.Tests.csproj │ └── SenderTest.cs ├── appveyor.yml ├── readme.md ├── samples ├── SDL.SignalR.OracleMessageBus.Client │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ └── SDL.SignalR.OracleMessageBus.Client.csproj └── SDL.SignalR.OracleMessageBus.Server │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── SDL.SignalR.OracleMessageBus.Server.csproj ├── shared └── Sdl.SignalR.OracleMessageBus.snk ├── src └── SDL.SignalR.OracleMessageBus │ ├── DbOperation │ ├── DataRecordExtensions.cs │ ├── DbCommandExtensions.cs │ ├── DbConnectionExtension.cs │ ├── DbOperation.cs │ ├── DbOperationFactory.cs │ ├── DbProviderFactoryAdapter.cs │ ├── DbProviderFactoryExtensions.cs │ ├── NotificationState.cs │ ├── ObservableDbOperation.cs │ ├── ObservableDbOperationFactory.cs │ ├── OracleDependencyManager.cs │ ├── OracleMessageBusException.cs │ ├── SignalRDbNotificationEventArgs.cs │ ├── SignalrDbDependency.cs │ └── SignalrDbDependencyFactory.cs │ ├── DependencyResolverExtensions.cs │ ├── Helpers │ └── TaskAsyncHelper.cs │ ├── Interfaces │ ├── IDataParameterExtensions.cs │ ├── IDbBehavior.cs │ ├── IDbOperation.cs │ ├── IDbOperationFactory.cs │ ├── IDbProviderFactory.cs │ ├── IObservableDbOperation.cs │ ├── IObservableDbOperationFactory.cs │ ├── IOracleDependencyManager.cs │ ├── IOracleReceiver.cs │ ├── IOracleSender.cs │ ├── ISignalRDbDependency.cs │ ├── ISignalRDbNotificationEventArgs.cs │ └── ISignalrDbDependencyFactory.cs │ ├── MessageWrapper.cs │ ├── OracleInstaller.cs │ ├── OracleMessageBus.cs │ ├── OraclePayload.cs │ ├── OracleReceiver.cs │ ├── OracleScaleoutConfiguration.cs │ ├── OracleSender.cs │ ├── OracleStream.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── SDL.SignalR.OracleMessageBus.csproj │ └── install.sql └── test.runsettings /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | 24 | # Visual Studio 2015 cache/options directory 25 | .vs/ 26 | 27 | # MSTest test Results 28 | [Tt]est[Rr]esult*/ 29 | [Bb]uild[Ll]og.* 30 | 31 | # NUNIT 32 | *.VisualState.xml 33 | TestResult.xml 34 | 35 | # Build Results of an ATL Project 36 | [Dd]ebugPS/ 37 | [Rr]eleasePS/ 38 | dlldata.c 39 | 40 | # DNX 41 | project.lock.json 42 | artifacts/ 43 | 44 | *_i.c 45 | *_p.c 46 | *_i.h 47 | *.ilk 48 | *.meta 49 | *.obj 50 | *.pch 51 | *.pdb 52 | *.pgc 53 | *.pgd 54 | *.rsp 55 | *.sbr 56 | *.tlb 57 | *.tli 58 | *.tlh 59 | *.tmp 60 | *.tmp_proj 61 | *.log 62 | *.vspscc 63 | *.vssscc 64 | .builds 65 | *.pidb 66 | *.svclog 67 | *.scc 68 | 69 | # Chutzpah Test files 70 | _Chutzpah* 71 | 72 | # Visual C++ cache files 73 | ipch/ 74 | *.aps 75 | *.ncb 76 | *.opensdf 77 | *.sdf 78 | *.cachefile 79 | 80 | # Visual Studio profiler 81 | *.psess 82 | *.vsp 83 | *.vspx 84 | 85 | # TFS 2012 Local Workspace 86 | $tf/ 87 | 88 | # Guidance Automation Toolkit 89 | *.gpState 90 | 91 | # ReSharper is a .NET coding add-in 92 | _ReSharper*/ 93 | *.[Rr]e[Ss]harper 94 | *.DotSettings.user 95 | 96 | # JustCode is a .NET coding add-in 97 | .JustCode 98 | 99 | # TeamCity is a build add-in 100 | _TeamCity* 101 | 102 | # DotCover is a Code Coverage Tool 103 | *.dotCover 104 | 105 | # NCrunch 106 | _NCrunch_* 107 | .*crunch*.local.xml 108 | 109 | # MightyMoose 110 | *.mm.* 111 | AutoTest.Net/ 112 | 113 | # Web workbench (sass) 114 | .sass-cache/ 115 | 116 | # Installshield output folder 117 | [Ee]xpress/ 118 | 119 | # DocProject is a documentation generator add-in 120 | DocProject/buildhelp/ 121 | DocProject/Help/*.HxT 122 | DocProject/Help/*.HxC 123 | DocProject/Help/*.hhc 124 | DocProject/Help/*.hhk 125 | DocProject/Help/*.hhp 126 | DocProject/Help/Html2 127 | DocProject/Help/html 128 | 129 | # Click-Once directory 130 | publish/ 131 | 132 | # Publish Web Output 133 | *.[Pp]ublish.xml 134 | *.azurePubxml 135 | ## TODO: Comment the next line if you want to checkin your 136 | ## web deploy settings but do note that will include unencrypted 137 | ## passwords 138 | #*.pubxml 139 | 140 | *.publishproj 141 | 142 | # NuGet Packages 143 | *.nupkg 144 | # The packages folder can be ignored because of Package Restore 145 | **/packages/* 146 | # except build/, which is used as an MSBuild target. 147 | !**/packages/build/ 148 | # Uncomment if necessary however generally it will be regenerated when needed 149 | !**/packages/repositories.config 150 | 151 | # Windows Azure Build Output 152 | csx/ 153 | *.build.csdef 154 | 155 | # Windows Store app package directory 156 | AppPackages/ 157 | 158 | # Visual Studio cache files 159 | # files ending in .cache can be ignored 160 | *.[Cc]ache 161 | # but keep track of directories ending in .cache 162 | !*.[Cc]ache/ 163 | 164 | # Others 165 | ClientBin/ 166 | [Ss]tyle[Cc]op.* 167 | ~$* 168 | *~ 169 | *.dbmdl 170 | *.dbproj.schemaview 171 | *.pfx 172 | *.publishsettings 173 | node_modules/ 174 | orleans.codegen.cs 175 | 176 | # RIA/Silverlight projects 177 | Generated_Code/ 178 | 179 | # Backup & report files from converting an old project file 180 | # to a newer Visual Studio version. Backup files are not needed, 181 | # because we have git ;-) 182 | _UpgradeReport_Files/ 183 | Backup*/ 184 | UpgradeLog*.XML 185 | UpgradeLog*.htm 186 | 187 | # SQL Server files 188 | *.mdf 189 | *.ldf 190 | 191 | # Business Intelligence projects 192 | *.rdl.data 193 | *.bim.layout 194 | *.bim_*.settings 195 | 196 | # Microsoft Fakes 197 | FakesAssemblies/ 198 | 199 | # Node.js Tools for Visual Studio 200 | .ntvs_analysis.dat 201 | 202 | # Visual Studio 6 build log 203 | *.plg 204 | 205 | # Visual Studio 6 workspace options file 206 | *.opt 207 | 208 | # LightSwitch generated files 209 | GeneratedArtifacts/ 210 | _Pvt_Extensions/ 211 | ModelManifest.xml 212 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.0.4.0 4 | 2.0.4.0 5 | 2.0.4.0 6 | RWS Group for and on behalf of its affiliates and subsidiaries 7 | RWS Group for and on behalf of its affiliates and subsidiaries 8 | Copyright © 2015-2023 All Rights Reserved by the RWS Group for and on behalf of its affiliates and subsidiaries. 9 | http://dr0muzwhcp26z.cloudfront.net/static/corporate/SDL-logo-2014.png 10 | https://community.sdl.com/developers/tridion_developer/w/wiki/864.sdl-web-developer-software-and-distribution-agreement 11 | https://github.com/sdl/SignalR-OracleMessageBus 12 | Initial release of Oracle backplane which supports only single stream. 13 | SDL,SignalR,Backplane,MessageBus,Oracle 14 | true 15 | 16 | -------------------------------------------------------------------------------- /SDL.SignalR.OracleMessageBus.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26228.4 5 | MinimumVisualStudioVersion = 15.0.26228.4 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sdl.SignalR.OracleMessageBus", "src\Sdl.SignalR.OracleMessageBus\Sdl.SignalR.OracleMessageBus.csproj", "{286E7DED-376D-45A2-A1A6-E2DFC1431360}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DB8B4AA0-DB1F-4DFA-817F-582B2B88ED5A}" 9 | ProjectSection(SolutionItems) = preProject 10 | ServerRun.testsettings = ServerRun.testsettings 11 | test.runsettings = test.runsettings 12 | EndProjectSection 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdl.SignalR.OracleMessageBus.Tests", "Tests\Sdl.SignalR.OracleMessageBus.Tests\Sdl.SignalR.OracleMessageBus.Tests.csproj", "{99F760DE-2496-4A81-9C13-8D9A27623671}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{3112B7DE-0654-4030-B273-FB1026C49169}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{19276FA5-ECFE-4258-87C1-0D36C22EEFDF}" 19 | EndProject 20 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdl.SignalR.OracleMessageBus.Client", "samples\Sdl.SignalR.OracleMessageBus.Client\Sdl.SignalR.OracleMessageBus.Client.csproj", "{3F3C2EBB-E0D8-4CAF-9C1B-CAC5B0241C4C}" 21 | EndProject 22 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sdl.SignalR.OracleMessageBus.Server", "samples\Sdl.SignalR.OracleMessageBus.Server\Sdl.SignalR.OracleMessageBus.Server.csproj", "{E4ADC697-E387-4FBB-919C-6C4B2BF0FC19}" 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {286E7DED-376D-45A2-A1A6-E2DFC1431360}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {286E7DED-376D-45A2-A1A6-E2DFC1431360}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {286E7DED-376D-45A2-A1A6-E2DFC1431360}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {286E7DED-376D-45A2-A1A6-E2DFC1431360}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {99F760DE-2496-4A81-9C13-8D9A27623671}.Debug|Any CPU.ActiveCfg = Release|Any CPU 35 | {99F760DE-2496-4A81-9C13-8D9A27623671}.Debug|Any CPU.Build.0 = Release|Any CPU 36 | {99F760DE-2496-4A81-9C13-8D9A27623671}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {99F760DE-2496-4A81-9C13-8D9A27623671}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {3F3C2EBB-E0D8-4CAF-9C1B-CAC5B0241C4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {3F3C2EBB-E0D8-4CAF-9C1B-CAC5B0241C4C}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {3F3C2EBB-E0D8-4CAF-9C1B-CAC5B0241C4C}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {3F3C2EBB-E0D8-4CAF-9C1B-CAC5B0241C4C}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {E4ADC697-E387-4FBB-919C-6C4B2BF0FC19}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {E4ADC697-E387-4FBB-919C-6C4B2BF0FC19}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {E4ADC697-E387-4FBB-919C-6C4B2BF0FC19}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {E4ADC697-E387-4FBB-919C-6C4B2BF0FC19}.Release|Any CPU.Build.0 = Release|Any CPU 46 | EndGlobalSection 47 | GlobalSection(SolutionProperties) = preSolution 48 | HideSolutionNode = FALSE 49 | EndGlobalSection 50 | GlobalSection(NestedProjects) = preSolution 51 | {99F760DE-2496-4A81-9C13-8D9A27623671} = {3112B7DE-0654-4030-B273-FB1026C49169} 52 | {3F3C2EBB-E0D8-4CAF-9C1B-CAC5B0241C4C} = {19276FA5-ECFE-4258-87C1-0D36C22EEFDF} 53 | {E4ADC697-E387-4FBB-919C-6C4B2BF0FC19} = {19276FA5-ECFE-4258-87C1-0D36C22EEFDF} 54 | EndGlobalSection 55 | GlobalSection(ExtensibilityGlobals) = postSolution 56 | SolutionGuid = {CB7DDD79-421A-4002-8B50-9AF4C2200576} 57 | EndGlobalSection 58 | EndGlobal 59 | -------------------------------------------------------------------------------- /ServerRun.testsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | These are default test settings for a local test run. 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/DbOperationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Diagnostics; 4 | using FakeItEasy; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace Sdl.SignalR.OracleMessageBus.Tests 8 | { 9 | [TestClass] 10 | public class DbOperationTest 11 | { 12 | [TestMethod] 13 | public void OpenDispose_Basic_Success() 14 | { 15 | bool[] dbReaderReads = { true, true, false }; 16 | 17 | var fakeDbReader = A.Fake(); 18 | var fakeDbReaderReadCall = A.CallTo(() => fakeDbReader.Read()); 19 | fakeDbReaderReadCall.ReturnsNextFromSequence(dbReaderReads); 20 | 21 | var fakeDbCommand = A.Fake(); 22 | A.CallTo(() => fakeDbCommand.ExecuteNonQuery()).Returns(2); 23 | A.CallTo(() => fakeDbCommand.ExecuteScalar()).Returns(3); 24 | A.CallTo(() => fakeDbCommand.ExecuteReader()).Returns(fakeDbReader); 25 | 26 | var fakeConnection = A.Fake(); 27 | var fakeConnectionDisposeCall = A.CallTo(() => fakeConnection.Dispose()); 28 | var fakeConnectionOpenCall = A.CallTo(() => fakeConnection.Open()); 29 | A.CallTo(() => fakeConnection.CreateCommand()) 30 | .Returns(fakeDbCommand); 31 | 32 | IDbProviderFactory fakeDbProviderFactory = A.Fake(); 33 | var createConnectionCall = A.CallTo(() => fakeDbProviderFactory.CreateConnection()); 34 | createConnectionCall.Returns(fakeConnection); 35 | 36 | DbOperation dbOperation = new DbOperation(string.Empty, string.Empty, new TraceSource("ss"), fakeDbProviderFactory); 37 | dbOperation.ExecuteNonQuery(); 38 | int readCount = dbOperation.ExecuteReader(A.Fake>()); 39 | 40 | Assert.AreEqual(dbReaderReads.Length - 1, readCount); 41 | 42 | createConnectionCall.MustHaveHappened(Repeated.Exactly.Twice); 43 | fakeConnectionOpenCall.MustHaveHappened(Repeated.Exactly.Twice); 44 | fakeConnectionDisposeCall.MustHaveHappened(Repeated.Exactly.Twice); 45 | } 46 | 47 | [TestMethod] 48 | public void DbExceptionThrown() 49 | { 50 | string msg = Guid.NewGuid().ToString("N"); 51 | 52 | var fakeDbCommand = A.Fake(); 53 | A.CallTo(() => fakeDbCommand.ExecuteNonQuery()).Throws(new Exception(msg)); 54 | A.CallTo(() => fakeDbCommand.ExecuteScalar()).Throws(new Exception(msg)); 55 | A.CallTo(() => fakeDbCommand.ExecuteReader()).Throws(new Exception(msg)); 56 | 57 | var fakeConnection = A.Fake(); 58 | A.CallTo(() => fakeConnection.CreateCommand()) 59 | .Returns(fakeDbCommand); 60 | 61 | IDbProviderFactory fakeDbProviderFactory = A.Fake(); 62 | var createConnectionCall = A.CallTo(() => fakeDbProviderFactory.CreateConnection()); 63 | createConnectionCall.Returns(fakeConnection); 64 | 65 | DbOperation dbOperation = new DbOperation(string.Empty, string.Empty, new TraceSource("ss"), fakeDbProviderFactory); 66 | 67 | try 68 | { 69 | dbOperation.ExecuteNonQuery(); 70 | Assert.Fail("Expected exception was not thrown."); 71 | } 72 | catch (Exception e) 73 | { 74 | Assert.AreEqual(msg, e.Message); 75 | } 76 | 77 | try 78 | { 79 | dbOperation.ExecuteScalar(); 80 | Assert.Fail("Expected exception was not thrown."); 81 | } 82 | catch (Exception e) 83 | { 84 | Assert.AreEqual(msg, e.Message); 85 | } 86 | 87 | try 88 | { 89 | dbOperation.ExecuteReader(A.Fake>()); 90 | Assert.Fail("Expected exception was not thrown."); 91 | } 92 | catch (Exception e) 93 | { 94 | Assert.AreEqual(msg, e.Message); 95 | } 96 | 97 | try 98 | { 99 | dbOperation.ExecuteNonQueryAsync().Wait(); 100 | Assert.Fail("Expected exception was not thrown."); 101 | } 102 | catch (Exception e) 103 | { 104 | Assert.AreEqual(msg, e.Message); 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/DbProviderFactoryAdapterTest.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Common; 2 | using FakeItEasy; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus.Tests 6 | { 7 | [TestClass] 8 | public class DbProviderFactoryAdapterTest 9 | { 10 | [TestMethod] 11 | public void DbProviderFactoryAdapterCreateConnection() 12 | { 13 | DbProviderFactory dbProviderFactory = A.Fake(c => c.Strict()); 14 | DbConnection iDbConnection = A.Fake(); 15 | 16 | DbProviderFactoryAdapter dbProviderFactoryAdapter = new DbProviderFactoryAdapter(dbProviderFactory); 17 | var fakeDbProviderFactoryAdapter = A.CallTo(() => dbProviderFactory.CreateConnection()); 18 | 19 | fakeDbProviderFactoryAdapter.Returns(iDbConnection); 20 | dbProviderFactoryAdapter.CreateConnection(); 21 | 22 | fakeDbProviderFactoryAdapter.MustHaveHappened(Repeated.Exactly.Once); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/ObservableDbOperationTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Diagnostics; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using FakeItEasy; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | using Oracle.ManagedDataAccess.Client; 10 | 11 | namespace Sdl.SignalR.OracleMessageBus.Tests 12 | { 13 | [TestClass] 14 | public class ObservableDbOperationTest 15 | { 16 | private Tuple[] _updateLoopRetryDelays = 17 | { 18 | Tuple.Create(0, 3), // 0ms x 3 19 | Tuple.Create(10, 3), // 10ms x 3 20 | }; 21 | 22 | [TestMethod] 23 | public void Basic_Success() 24 | { 25 | var fakeDbBehavior = A.Fake(); 26 | A.CallTo(() => fakeDbBehavior.UpdateLoopRetryDelays).Returns(_updateLoopRetryDelays); 27 | 28 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, new TraceSource("ss"), 29 | fakeDbBehavior, 30 | A.Fake(), 31 | A.Fake(), 32 | A.Fake(), 33 | true); 34 | 35 | dbOperation.ExecuteReaderWithUpdates(A.Fake>()); 36 | } 37 | 38 | [TestMethod] 39 | public void CreateDependency_Success() 40 | { 41 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, new TraceSource("ss"), 42 | null, 43 | A.Fake(), 44 | A.Fake(), 45 | A.Fake(), 46 | true); 47 | 48 | dbOperation.ExecuteReaderWithUpdates(A.Fake>()); 49 | } 50 | 51 | [TestMethod] 52 | public void Pooling_Success() 53 | { 54 | var fakeDbReader = A.Fake(); 55 | var fakeDbReaderReadCall = A.CallTo(() => fakeDbReader.Read()); 56 | fakeDbReaderReadCall.ReturnsNextFromSequence(true, true, false); 57 | 58 | var fakeEmptyDbReader = A.Fake(); 59 | A.CallTo(() => fakeEmptyDbReader.Read()).Returns(false); 60 | 61 | var fakeDbCommand = A.Fake(); 62 | A.CallTo(() => fakeDbCommand.ExecuteReader()).ReturnsNextFromSequence(fakeDbReader, fakeEmptyDbReader); 63 | 64 | var fakeDbConnection = A.Fake(); 65 | A.CallTo(() => fakeDbConnection.CreateCommand()).Returns(fakeDbCommand); 66 | var fakeDbConnectionDisposeCall = A.CallTo(() => fakeDbConnection.Dispose()); 67 | 68 | var fakeDbProviderFactory = A.Fake(); 69 | A.CallTo(() => fakeDbProviderFactory.CreateConnection()).Returns(fakeDbConnection); 70 | 71 | var fakeProcessRecordAction = A.Fake>(); 72 | var fakeProcessRecordActionInvokeCall = A.CallTo(() => fakeProcessRecordAction.Invoke(null, null)).WithAnyArguments(); 73 | 74 | var fakeDbBehavior = A.Fake(); 75 | A.CallTo(() => fakeDbBehavior.UpdateLoopRetryDelays).Returns(_updateLoopRetryDelays); 76 | 77 | var fakeOracleDependencyManager = A.Fake(); 78 | var fakeOracleDependencyManagerRegistryDepCall = A.CallTo(() => fakeOracleDependencyManager.RegisterDependency(null)).WithAnyArguments(); 79 | 80 | var fakeOracleDependencyManagerRemoveRegistrationCall = 81 | A.CallTo(() => fakeOracleDependencyManager.RemoveRegistration(string.Empty)).WithAnyArguments(); 82 | 83 | var fakeSignalrDbDependencyFactory = A.Fake(); 84 | var fakeSignalrDbDependencyFactoryCreateDepCall = A.CallTo(() => fakeSignalrDbDependencyFactory.CreateDbDependency(null, false, 0, false)).WithAnyArguments(); 85 | 86 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, new TraceSource("ss"), 87 | fakeDbBehavior, 88 | fakeDbProviderFactory, 89 | fakeOracleDependencyManager, 90 | fakeSignalrDbDependencyFactory, 91 | false); 92 | 93 | using (var cts = new CancellationTokenSource()) 94 | { 95 | Task.Run(() => dbOperation.ExecuteReaderWithUpdates(fakeProcessRecordAction), 96 | cts.Token); 97 | Thread.Sleep(1000); 98 | cts.Cancel(); 99 | } 100 | 101 | Thread.Sleep(1000); 102 | fakeOracleDependencyManagerRegistryDepCall.MustNotHaveHappened(); 103 | fakeSignalrDbDependencyFactoryCreateDepCall.MustNotHaveHappened(); 104 | fakeProcessRecordActionInvokeCall.MustHaveHappened(); 105 | fakeDbReaderReadCall.MustHaveHappened(Repeated.Exactly.Times(3)); 106 | fakeDbConnectionDisposeCall.MustHaveHappened(); 107 | 108 | dbOperation.Dispose(); 109 | 110 | fakeOracleDependencyManagerRemoveRegistrationCall.MustHaveHappened(Repeated.Exactly.Once); 111 | } 112 | 113 | private class FakeSignalrDependency : ISignalRDbDependency 114 | { 115 | public void FireEvent() 116 | { 117 | if (OnChanged != null) 118 | { 119 | OnChanged(this, A.Fake()); 120 | } 121 | } 122 | 123 | public void FireEvent(OracleNotificationType notificationType, OracleNotificationInfo notificationInfo) 124 | { 125 | var notificationArgsFake = A.Fake(); 126 | A.CallTo(() => notificationArgsFake.NotificationType).Returns((int)notificationType); 127 | A.CallTo(() => notificationArgsFake.NotificationInfo).Returns((int)notificationInfo); 128 | 129 | if (OnChanged != null) 130 | { 131 | OnChanged(this, notificationArgsFake); 132 | } 133 | } 134 | 135 | public event EventHandler OnChanged; 136 | public void RemoveRegistration(OracleConnection conn) 137 | { 138 | } 139 | } 140 | 141 | [TestMethod] 142 | public void DependencyOnChange() 143 | { 144 | var fakeOracleDependencyManager = A.Fake(); 145 | var fakeOracleDependencyManagerRegistryDepCall = A.CallTo(() => fakeOracleDependencyManager.RegisterDependency(null)).WithAnyArguments(); 146 | 147 | var fakeSignalrDbDependencyFactory = A.Fake(); 148 | var fakeSignalrDbDependencyFactoryCreateDepCall = A.CallTo(() => fakeSignalrDbDependencyFactory.CreateDbDependency(null, false, 0, false)).WithAnyArguments(); 149 | 150 | var fakeSignalrDependency = new FakeSignalrDependency(); 151 | fakeSignalrDbDependencyFactoryCreateDepCall.Returns(fakeSignalrDependency); 152 | 153 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, new TraceSource("ss"), 154 | null, 155 | A.Fake(), 156 | fakeOracleDependencyManager, 157 | fakeSignalrDbDependencyFactory, 158 | true); 159 | 160 | dbOperation.ExecuteReaderWithUpdates(A.Fake>()); 161 | 162 | int changedCounter = 0; 163 | dbOperation.Changed += () => { changedCounter++; }; 164 | int faultCounter = 0; 165 | dbOperation.Faulted += ex => { faultCounter++; }; 166 | 167 | fakeSignalrDependency.FireEvent(); 168 | 169 | Assert.AreEqual(1, changedCounter); 170 | Assert.AreEqual(0, faultCounter); 171 | fakeOracleDependencyManagerRegistryDepCall.MustHaveHappened(Repeated.Exactly.Once); 172 | fakeSignalrDbDependencyFactoryCreateDepCall.MustHaveHappened(Repeated.Exactly.Once); 173 | } 174 | 175 | [TestMethod] 176 | public void DependencyFault() 177 | { 178 | var fakeOracleDependencyManager = A.Fake(); 179 | var fakeOracleDependencyManagerRegistryDepCall = A.CallTo(() => fakeOracleDependencyManager.RegisterDependency(null)).WithAnyArguments(); 180 | 181 | var fakeSignalrDbDependencyFactory = A.Fake(); 182 | var fakeSignalrDbDependencyFactoryCreateDepCall = A.CallTo(() => fakeSignalrDbDependencyFactory.CreateDbDependency(null, false, 0, false)).WithAnyArguments(); 183 | 184 | var fakeSignalrDependency = new FakeSignalrDependency(); 185 | fakeSignalrDbDependencyFactoryCreateDepCall.Returns(fakeSignalrDependency); 186 | 187 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, new TraceSource("ss"), 188 | null, 189 | A.Fake(), 190 | fakeOracleDependencyManager, 191 | fakeSignalrDbDependencyFactory, 192 | true); 193 | 194 | dbOperation.ExecuteReaderWithUpdates(A.Fake>()); 195 | 196 | int faultCounter = 0; 197 | dbOperation.Faulted += ex => { if (ex != null) faultCounter++; }; 198 | 199 | fakeSignalrDependency.FireEvent(OracleNotificationType.Change, OracleNotificationInfo.Error); 200 | 201 | Assert.AreEqual(1, faultCounter); 202 | fakeOracleDependencyManagerRegistryDepCall.MustHaveHappened(Repeated.Exactly.Once); 203 | fakeSignalrDbDependencyFactoryCreateDepCall.MustHaveHappened(Repeated.Exactly.Once); 204 | } 205 | 206 | [TestMethod] 207 | public void RemoveRegistrationDependency() 208 | { 209 | var fakeOracleDependencyManager = A.Fake(); 210 | var fakeOracleDependencyManagerRegistryDepCall = A.CallTo(() => fakeOracleDependencyManager.RegisterDependency(null)).WithAnyArguments(); 211 | 212 | var fakeOracleDependencyManagerRemoveRegistrationCall = 213 | A.CallTo(() => fakeOracleDependencyManager.RemoveRegistration(string.Empty)).WithAnyArguments(); 214 | 215 | var fakeSignalrDbDependencyFactory = A.Fake(); 216 | var fakeSignalrDbDependencyFactoryCreateDepCall = A.CallTo(() => fakeSignalrDbDependencyFactory.CreateDbDependency(null, false, 0, false)).WithAnyArguments(); 217 | 218 | var fakeSignalrDependency = new FakeSignalrDependency(); 219 | fakeSignalrDbDependencyFactoryCreateDepCall.Returns(fakeSignalrDependency); 220 | 221 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, new TraceSource("ss"), 222 | null, 223 | A.Fake(), 224 | fakeOracleDependencyManager, 225 | fakeSignalrDbDependencyFactory, 226 | true); 227 | 228 | dbOperation.ExecuteReaderWithUpdates(A.Fake>()); 229 | 230 | fakeSignalrDependency.FireEvent(OracleNotificationType.Subscribe, OracleNotificationInfo.Error); 231 | 232 | fakeOracleDependencyManagerRegistryDepCall.MustHaveHappened(Repeated.Exactly.Once); 233 | fakeSignalrDbDependencyFactoryCreateDepCall.MustHaveHappened(Repeated.Exactly.Once); 234 | fakeOracleDependencyManagerRemoveRegistrationCall.MustHaveHappened(Repeated.Exactly.Once); 235 | } 236 | 237 | [TestMethod] 238 | public void ExecuteReaderWithUpdates_Fail() 239 | { 240 | var fakeOracleDependencyManager = A.Fake(); 241 | var fakeSignalrDbDependencyFactory = A.Fake(); 242 | 243 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, 244 | new TraceSource("ss"), 245 | null, 246 | A.Fake(), 247 | fakeOracleDependencyManager, 248 | fakeSignalrDbDependencyFactory, 249 | true); 250 | 251 | dbOperation.Queried += () => { throw new Exception(); }; 252 | int counter = 0; 253 | dbOperation.Faulted += (ex) => { counter++; }; 254 | 255 | using (var cts = new CancellationTokenSource()) 256 | { 257 | Task.Run(() => dbOperation.ExecuteReaderWithUpdates(A.Fake>()), 258 | cts.Token); 259 | Thread.Sleep(1000); 260 | dbOperation.Dispose(); 261 | cts.Cancel(); 262 | } 263 | 264 | Assert.IsTrue(counter > 0); 265 | } 266 | 267 | [TestMethod] 268 | public void UpdateDependency() 269 | { 270 | var fakeOracleDependencyManager = A.Fake(); 271 | var fakeOracleDependencyManagerRegistryDepCall = 272 | A.CallTo(() => fakeOracleDependencyManager.RegisterDependency(null)).WithAnyArguments(); 273 | 274 | var fakeSignalrDbDependencyFactory = A.Fake(); 275 | var fakeSignalrDbDependencyFactoryCreateDepCall = 276 | A.CallTo(() => fakeSignalrDbDependencyFactory.CreateDbDependency(null, false, 0, false)) 277 | .WithAnyArguments(); 278 | 279 | var fakeSignalrDependency = new FakeSignalrDependency(); 280 | fakeSignalrDbDependencyFactoryCreateDepCall.Returns(fakeSignalrDependency); 281 | 282 | TraceSource trace = new TraceSource("Fault"); 283 | FakeTraceListener fakeListener = new FakeTraceListener(); 284 | trace.Listeners.Add(fakeListener); 285 | trace.Switch.Level = SourceLevels.All; 286 | 287 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, trace, 288 | null, 289 | A.Fake(), 290 | fakeOracleDependencyManager, 291 | fakeSignalrDbDependencyFactory, 292 | true); 293 | 294 | dbOperation.ExecuteReaderWithUpdates(A.Fake>()); 295 | fakeSignalrDependency.FireEvent(OracleNotificationType.Change, OracleNotificationInfo.Update); 296 | int counter = 0; 297 | dbOperation.Faulted += (ex) => { counter++; }; 298 | Assert.AreEqual(0, counter); 299 | Assert.IsTrue(fakeListener.Traces.Exists(item => item.StartsWith("Oracle notification details:"))); 300 | 301 | fakeSignalrDependency.FireEvent(OracleNotificationType.Change, OracleNotificationInfo.End); 302 | Assert.AreEqual(0, counter); 303 | Assert.IsTrue(fakeListener.Traces.Exists(item => item.StartsWith("Oracle notification timed out"))); 304 | 305 | fakeSignalrDependency.FireEvent(OracleNotificationType.Change, OracleNotificationInfo.Drop); 306 | Assert.AreEqual(1, counter); 307 | Assert.IsTrue(fakeListener.Traces.Exists(item => item.StartsWith("Unexpected Oracle notification details:"))); 308 | 309 | fakeOracleDependencyManagerRegistryDepCall.MustHaveHappened(Repeated.AtLeast.Once); 310 | fakeSignalrDbDependencyFactoryCreateDepCall.MustHaveHappened(Repeated.AtLeast.Once); 311 | 312 | } 313 | 314 | [TestMethod] 315 | public void CatchQLreceiveloop() 316 | { 317 | 318 | // Configure IDbBehaviour to issue OracleException during AddOracleDependency 319 | var fakeDbBehavior = A.Fake(); 320 | A.CallTo(() => fakeDbBehavior.AddOracleDependency(null, null)) 321 | .WithAnyArguments() 322 | .Throws(new Exception("Hello, World!") ); 323 | A.CallTo(() => fakeDbBehavior.UpdateLoopRetryDelays).Returns(_updateLoopRetryDelays); 324 | 325 | // Define trace source with our listener to collect trace messages 326 | var traceSource = new TraceSource("ss"); 327 | FakeTraceListener fakeListener = new FakeTraceListener(); 328 | traceSource.Listeners.Add(fakeListener); 329 | traceSource.Switch.Level = SourceLevels.All; 330 | 331 | var fakeOracleDependencyManager = A.Fake(); 332 | var fakeSignalrDbDependencyFactory = A.Fake(); 333 | var fakeDbProviderFactory = A.Fake(); 334 | 335 | ObservableDbOperation dbOperation = new ObservableDbOperation(string.Empty, string.Empty, traceSource, 336 | fakeDbBehavior, 337 | fakeDbProviderFactory, 338 | fakeOracleDependencyManager, 339 | fakeSignalrDbDependencyFactory, 340 | useOracleDependency: true); 341 | 342 | Action action = (object obj) => 343 | { 344 | dbOperation.ExecuteReaderWithUpdates(A.Fake>()); 345 | }; 346 | 347 | CancellationTokenSource source = new CancellationTokenSource(); 348 | CancellationToken token = source.Token; 349 | 350 | Task task = new Task(action, "ss", token); 351 | task.Start(); 352 | task.Wait(TimeSpan.FromMilliseconds(5000)); 353 | source.Cancel(); 354 | Assert.IsNull(task.Exception); 355 | // Don`t know why it is here 356 | // Assert.IsNotInstanceOfType(task.Exception, typeof(AggregateException)); 357 | Assert.IsTrue(fakeListener.Traces.Exists(item => item.StartsWith("Error in SQL receive loop"))); 358 | 359 | } 360 | 361 | private class FakeTraceListener : TraceListener 362 | { 363 | public readonly List Traces = new List(); 364 | 365 | public override void Write(string message) 366 | { 367 | Traces.Add(message); 368 | } 369 | 370 | public override void WriteLine(string message) 371 | { 372 | Traces.Add(message); 373 | } 374 | } 375 | 376 | } 377 | } 378 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/OracleDependencyManagerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Diagnostics; 4 | using FakeItEasy; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace Sdl.SignalR.OracleMessageBus.Tests 8 | { 9 | [TestClass] 10 | public class OracleDependencyManagerTest 11 | { 12 | [TestMethod] 13 | public void Basic_Success() 14 | { 15 | OracleDependencyManager depManager = new OracleDependencyManager(A.Fake(), new TraceSource("ss")); 16 | depManager.RegisterDependency(null); 17 | } 18 | 19 | [TestMethod] 20 | public void AllDependenciesAreUnregisterd() 21 | { 22 | var fakeDbDependency = A.Fake(); 23 | var fakeDbDependencyRemoveRegistrationCall = A.CallTo(() => fakeDbDependency.RemoveRegistration(null)).WithAnyArguments(); 24 | 25 | var fakeDbConnection = A.Fake(); 26 | var fakeDbConnectionOpenCall = A.CallTo(() => fakeDbConnection.Open()); 27 | var fakeDbConnectionDisposeCall = A.CallTo(() => fakeDbConnection.Dispose()); 28 | 29 | var fakeDbProviderFactory = A.Fake(); 30 | A.CallTo(() => fakeDbProviderFactory.CreateConnection()).Returns(fakeDbConnection); 31 | var depManager = new OracleDependencyManager(fakeDbProviderFactory, new TraceSource("ss")); 32 | depManager.RegisterDependency(fakeDbDependency); 33 | depManager.RegisterDependency(fakeDbDependency); 34 | depManager.RemoveRegistration(string.Empty); 35 | 36 | fakeDbConnectionOpenCall.MustHaveHappened(Repeated.Exactly.Once); 37 | fakeDbDependencyRemoveRegistrationCall.MustHaveHappened(Repeated.Exactly.Twice); 38 | fakeDbConnectionDisposeCall.MustHaveHappened(Repeated.Exactly.Once); 39 | } 40 | 41 | [TestMethod] 42 | public void ErrorDuringRemovingDependency() 43 | { 44 | var fakeDbDependency = A.Fake(); 45 | var fakeDbDependencyRemoveRegistrationCall = A.CallTo(() => fakeDbDependency.RemoveRegistration(null)).WithAnyArguments(); 46 | 47 | var fakeErrorDbDependency = A.Fake(); 48 | A.CallTo(() => fakeDbDependency.RemoveRegistration(null)).WithAnyArguments().Throws(new Exception()); 49 | 50 | var fakeDbConnection = A.Fake(); 51 | var fakeDbConnectionOpenCall = A.CallTo(() => fakeDbConnection.Open()); 52 | 53 | var fakeDbProviderFactory = A.Fake(); 54 | A.CallTo(() => fakeDbProviderFactory.CreateConnection()).Returns(fakeDbConnection); 55 | var depManager = new OracleDependencyManager(fakeDbProviderFactory, new TraceSource("ss")); 56 | depManager.RegisterDependency(fakeErrorDbDependency); 57 | depManager.RegisterDependency(fakeDbDependency); 58 | depManager.RemoveRegistration(string.Empty); 59 | 60 | fakeDbConnectionOpenCall.MustHaveHappened(Repeated.Exactly.Once); 61 | fakeDbDependencyRemoveRegistrationCall.MustHaveHappened(Repeated.Exactly.Once); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/OracleInstallerTest.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using FakeItEasy; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus.Tests 6 | { 7 | [TestClass] 8 | public class OracleInstallerTest 9 | { 10 | [TestMethod] 11 | public void Install_Basic_Success() 12 | { 13 | var fakeDbProviderFactory = A.Fake(); 14 | var fakeDbOperationFactory = A.Fake(); 15 | 16 | var fakeDbOperation = A.Fake(); 17 | var createDbOperationCall = 18 | A.CallTo(() => fakeDbOperationFactory.CreateDbOperation("connectionString", "databaseScript", null, fakeDbProviderFactory)); 19 | createDbOperationCall.WithAnyArguments().Returns(fakeDbOperation); 20 | 21 | OracleInstaller installer = new OracleInstaller("USER ID=TestUser", new TraceSource("ts"), fakeDbProviderFactory, fakeDbOperationFactory); 22 | installer.Install(); 23 | 24 | createDbOperationCall.MustHaveHappened(Repeated.Exactly.Once); 25 | A.CallTo(() => fakeDbOperation.ExecuteNonQuery()).MustHaveHappened(Repeated.Exactly.Once); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | [assembly: AssemblyConfiguration("")] 4 | [assembly: AssemblyTrademark("")] 5 | [assembly: AssemblyCulture("")] 6 | 7 | // Setting ComVisible to false makes the types in this assembly not visible 8 | // to COM components. If you need to access a type in this assembly from 9 | // COM, set the ComVisible attribute to true on that type. 10 | [assembly: ComVisible(false)] 11 | 12 | // The following GUID is for the ID of the typelib if this project is exposed to COM 13 | [assembly: Guid("8337cef5-7a02-4ff8-bb45-14038e17de8d")] 14 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/ReceiverTest.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Diagnostics; 3 | using System.Threading; 4 | using FakeItEasy; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | 7 | namespace Sdl.SignalR.OracleMessageBus.Tests 8 | { 9 | [TestClass] 10 | public class ReceiverTest 11 | { 12 | [TestMethod] 13 | public void Receive_StartReceiving_Success() 14 | { 15 | var fakeDbProviderFactory = A.Fake(); 16 | var createParameterCall = A.CallTo(() => fakeDbProviderFactory.CreateParameter()); 17 | 18 | var fakeDataParameter = A.Fake(); 19 | var fakeDataParameterValueCall = A.CallTo(() => fakeDataParameter.Value); 20 | fakeDataParameterValueCall.Returns((long?) 1); 21 | createParameterCall.Returns(fakeDataParameter); 22 | 23 | var fakeDbOperation = A.Fake(); 24 | A.CallTo(() => fakeDbOperation.ExecuteScalar()) 25 | .Returns((long?)1); 26 | 27 | var fakeDbOperationFactory = A.Fake(); 28 | A.CallTo(() => fakeDbOperationFactory.CreateDbOperation("connStr", "command", new TraceSource("ts"), fakeDbProviderFactory)) 29 | .WithAnyArguments() 30 | .Returns(fakeDbOperation); 31 | 32 | IOracleReceiver receiver = new OracleReceiver("connStr", true, new TraceSource("ts"), fakeDbProviderFactory, fakeDbOperationFactory, A.Fake()); 33 | receiver.GetLastPayloadId(); 34 | receiver.StartReceivingUpdatesFromDb(); 35 | Thread.Sleep(100); 36 | 37 | createParameterCall.MustHaveHappened(Repeated.AtLeast.Once); 38 | } 39 | 40 | [TestMethod] 41 | public void Receiver_Dispose_Success() 42 | { 43 | IObservableDbOperationFactory FakeiDbOperationFactory = A.Fake(); 44 | var fakeIdbProviderFactoryOperation = A.Fake(); 45 | 46 | var fakeIDataParameter = A.Fake(); 47 | A.CallTo(() => fakeIDataParameter.Value).Returns((long)2); 48 | var fakeCallsTo = A.CallTo(() => fakeIdbProviderFactoryOperation.CreateParameter()).Returns(fakeIDataParameter); 49 | IOracleReceiver oracleReceiver = new OracleReceiver(string.Empty, true, new TraceSource("ss"), fakeIdbProviderFactoryOperation, A.Fake(), FakeiDbOperationFactory ); 50 | IObservableDbOperation fakDbOperation = A.Fake(); 51 | var fake = 52 | A.CallTo(() => FakeiDbOperationFactory.ObservableDbOperation(string.Empty, string.Empty, true, new TraceSource("ss"), 53 | A.Fake())).WithAnyArguments().Returns(fakDbOperation); 54 | 55 | var fakeIObservableDbOperationDispose = A.CallTo(() => fakDbOperation.Dispose()); 56 | oracleReceiver.GetLastPayloadId(); 57 | oracleReceiver.StartReceivingUpdatesFromDb(); 58 | Thread.Sleep(100); 59 | oracleReceiver.Dispose(); 60 | 61 | fakeIObservableDbOperationDispose.MustHaveHappened((Repeated.Exactly.Once)); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/SDL.SignalR.OracleMessageBus.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | Release 5 | Sdl.SignalR.OracleMessageBus.Tests 6 | Sdl.SignalR.OracleMessageBus.Tests 7 | true 8 | false 9 | ..\..\shared\Sdl.SignalR.OracleMessageBus.snk 10 | SDL.SignalR.OracleMessageBus.Tests 11 | false 12 | SDL.SignalR.OracleMessageBus.Tests 13 | 14 | false 15 | 16 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | Debug 35 | AnyCPU 36 | {99F760DE-2496-4A81-9C13-8D9A27623671} 37 | Library 38 | Properties 39 | 512 40 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 41 | 10.0 42 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 43 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 44 | False 45 | UnitTest 46 | 47 | true 48 | false 49 | ..\..\shared\Sdl.SignalR.OracleMessageBus.snk 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /Tests/SDL.SignalR.OracleMessageBus.Tests/SenderTest.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Diagnostics; 3 | using System.Collections.Generic; 4 | using FakeItEasy; 5 | using Microsoft.VisualStudio.TestTools.UnitTesting; 6 | using Microsoft.AspNet.SignalR.Messaging; 7 | 8 | namespace Sdl.SignalR.OracleMessageBus.Tests 9 | { 10 | [TestClass] 11 | public class SenderTest 12 | { 13 | [TestMethod] 14 | public void Send_EmptyList_Success() 15 | { 16 | var fakeDbOperation = A.Fake(); 17 | var executeNonQueryCall = A.CallTo(() => fakeDbOperation.ExecuteNonQueryAsync()); 18 | 19 | var fakeDbOperationFactory = A.Fake(); 20 | A.CallTo(() => fakeDbOperationFactory.CreateDbOperation("connStr", "table", new TraceSource("ts"), A.Fake())) 21 | .WithAnyArguments() 22 | .Returns(fakeDbOperation); 23 | 24 | IOracleSender sender = new OracleSender("connStr", new TraceSource("ts"), A.Fake(), fakeDbOperationFactory); 25 | sender.Send(new List()).Wait(); 26 | 27 | executeNonQueryCall.MustNotHaveHappened(); 28 | } 29 | 30 | [TestMethod] 31 | public void Send_OneMessage_Success() 32 | { 33 | var fakeDbOperation = A.Fake(); 34 | var executeNonQueryCall = A.CallTo(() => fakeDbOperation.ExecuteNonQueryAsync()); 35 | 36 | var fakeDbOperationFactory = A.Fake(); 37 | A.CallTo(() => fakeDbOperationFactory.CreateDbOperation("connStr", "table", new TraceSource("ts"), A.Fake(), A.Fake())) 38 | .WithAnyArguments() 39 | .Returns(fakeDbOperation); 40 | 41 | var messages = new List(); 42 | messages.Add(new Message("src", "key", "val")); 43 | 44 | IOracleSender sender = new OracleSender("connStr", new TraceSource("ts"), A.Fake(), fakeDbOperationFactory); 45 | sender.Send(messages); 46 | 47 | executeNonQueryCall.MustHaveHappened(Repeated.Exactly.Once); 48 | } 49 | 50 | [TestMethod] 51 | public void Send_MultipleMessages_Success() 52 | { 53 | var fakeDbOperation = A.Fake(); 54 | var executeNonQueryCall = A.CallTo(() => fakeDbOperation.ExecuteNonQueryAsync()); 55 | 56 | var fakeDbOperationFactory = A.Fake(); 57 | A.CallTo(() => fakeDbOperationFactory.CreateDbOperation("connStr", "table", new TraceSource("ts"), A.Fake(), A.Fake())) 58 | .WithAnyArguments() 59 | .Returns(fakeDbOperation); 60 | 61 | var messages = new List(); 62 | messages.Add(new Message("src1", "key1", "val1")); 63 | messages.Add(new Message("src2", "key2", "val2")); 64 | messages.Add(new Message("src3", "key3", "val3")); 65 | 66 | IOracleSender sender = new OracleSender("connStr", new TraceSource("ts"), A.Fake(), fakeDbOperationFactory); 67 | sender.Send(messages); 68 | 69 | executeNonQueryCall.MustHaveHappened(Repeated.Exactly.Once); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | configuration: Release 2 | image: Visual Studio 2019 3 | version: '2.0.{build}' 4 | before_build: 5 | - nuget restore 6 | artifacts: 7 | - path: 'src\**\$(configuration)\*.nupkg' 8 | name: NuGet 9 | deploy: 10 | provider: NuGet 11 | name: production 12 | on: 13 | branch: master 14 | api_key: 15 | secure: 4XQI0/GHWDexH86N+VfdwaB0iTFVJgfePU9l1Cv2Nx4li2mZKz/iMDknGUu62+YP 16 | skip_symbols: true -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | 2 | ![Build Badge](https://ci.appveyor.com/api/projects/status/github/sdl/SignalR-OracleMessageBus?svg=true) 3 | 4 | SDL SignalR Oracle backplane 5 | ===================== 6 | 7 | 8 | Oracle messaging backplane for scaling out of SignalR applications. 9 | 10 | ---------- 11 | 12 | 13 | About 14 | ------------- 15 | 16 | A lot of people are using Oracle as their database of choice, in which case, they cannot just use the same database as a backplane just like you can use SQL Server if that was your database of choice. An option could be just to use REDIS or some other supported bus as backplane, but, that does mean that an additional infrastructure and/or software requirement in order to use the component. 17 | 18 | For us as we support both Oracle and SQL Server, we wanted the experience for customers using Oracle the same as customers using SQL Server. Which meant that we needed to create an Oracle backplane. 19 | 20 | How To Use 21 | ------------- 22 | 23 | 1. Get the package Sdl.SignalR.OracleMessageBus from nuget.org. 24 | 2. Before your `app.MapSignalR` call use 25 | `GlobalHost.DependencyResolver.UseOracle("Data Source=ORA12101;User Id=testschema;Password=test123");` Replacing the connection string. 26 | 3. The package will create the necessary tables if they do not exist already. 27 | 28 | You can also enable Oracle Dependency if you want to by specifying additional parameter during the `UseOracle` call. 29 | 30 | Branches and Contributions 31 | ------------- 32 | * master - Represents the latest stable version. This may be a pre-release version 33 | * dev - Represents the current development branch. 34 | 35 | License 36 | ------------- 37 | 38 | Copyright (c) 2017 SDL Group. 39 | 40 | Licensed 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 41 | 42 | http://www.apache.org/licenses/LICENSE-2.0 43 | 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. -------------------------------------------------------------------------------- /samples/SDL.SignalR.OracleMessageBus.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNet.SignalR.Client; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus.Client 6 | { 7 | class Program 8 | { 9 | static void Main(string[] args) 10 | { 11 | HubConnection hubConnection = new HubConnection("http://localhost:8888/"); 12 | var hubProxy = hubConnection.CreateHubProxy("DummyHub"); 13 | int received = 0; 14 | int sent = 0; 15 | hubProxy.On("FooBack", str => 16 | { 17 | received++; 18 | if (received%1000 == 0) 19 | { 20 | Console.WriteLine("Received: {0}", received); 21 | } 22 | }); 23 | 24 | hubConnection.Start().Wait(); 25 | 26 | Parallel.For(0, 10, j => 27 | { 28 | for (int i = 0; i < 10000; i++) 29 | { 30 | hubProxy.Invoke("Foo", i.ToString()).Wait(); 31 | sent++; 32 | if (sent%1000 == 0) 33 | { 34 | Console.WriteLine("Sent: {0}", sent); 35 | } 36 | } 37 | }); 38 | 39 | 40 | Console.ReadLine(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /samples/SDL.SignalR.OracleMessageBus.Client/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | [assembly: AssemblyConfiguration("")] 4 | [assembly: AssemblyTrademark("")] 5 | [assembly: AssemblyCulture("")] 6 | 7 | // Setting ComVisible to false makes the types in this assembly not visible 8 | // to COM components. If you need to access a type in this assembly from 9 | // COM, set the ComVisible attribute to true on that type. 10 | [assembly: ComVisible(false)] 11 | 12 | // The following GUID is for the ID of the typelib if this project is exposed to COM 13 | [assembly: Guid("cb4dba8c-8cda-4662-b38c-c5c089da9966")] 14 | -------------------------------------------------------------------------------- /samples/SDL.SignalR.OracleMessageBus.Client/SDL.SignalR.OracleMessageBus.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | Debug;Release 5 | Sdl.SignalR.OracleMessageBus.Client 6 | Sdl.SignalR.OracleMessageBus.Client 7 | Exe 8 | SDL.SignalR.OracleMessageBus.Client 9 | SDL.SignalR.OracleMessageBus.Client 10 | 11 | false 12 | false 13 | 14 | 15 | AnyCPU 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | AnyCPU 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | Debug 35 | AnyCPU 36 | {3F3C2EBB-E0D8-4CAF-9C1B-CAC5B0241C4C} 37 | Exe 38 | Properties 39 | 512 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /samples/SDL.SignalR.OracleMessageBus.Server/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNet.SignalR; 2 | using Microsoft.Owin.Hosting; 3 | using Owin; 4 | using System; 5 | 6 | namespace Sdl.SignalR.OracleMessageBus.Server 7 | { 8 | class Program 9 | { 10 | static void Main(string[] args) 11 | { 12 | AppDomain.CurrentDomain.UnhandledException += (sender, eventArgs) => 13 | { 14 | Console.WriteLine(sender); 15 | Console.WriteLine(eventArgs); 16 | }; 17 | 18 | string url = "http://localhost:8888/"; 19 | using (WebApp.Start(url)) 20 | { 21 | Console.WriteLine("Backend Server running at {0}", url); 22 | Console.ReadLine(); 23 | } 24 | 25 | 26 | } 27 | } 28 | 29 | public class Startup 30 | { 31 | public void Configuration(IAppBuilder app) 32 | { 33 | var hubConfiguration = new HubConfiguration 34 | { 35 | EnableDetailedErrors = true 36 | }; 37 | 38 | GlobalHost.DependencyResolver.UseOracle("Data Source=ORA12101;User Id=testschema;Password=test123"); 39 | app.MapSignalR(hubConfiguration); 40 | } 41 | } 42 | 43 | public class DummyHub : Hub 44 | { 45 | public void Foo(string msg) 46 | { 47 | Clients.All.FooBack(msg); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /samples/SDL.SignalR.OracleMessageBus.Server/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | [assembly: AssemblyConfiguration("")] 4 | [assembly: AssemblyTrademark("")] 5 | [assembly: AssemblyCulture("")] 6 | 7 | // Setting ComVisible to false makes the types in this assembly not visible 8 | // to COM components. If you need to access a type in this assembly from 9 | // COM, set the ComVisible attribute to true on that type. 10 | [assembly: ComVisible(false)] 11 | 12 | // The following GUID is for the ID of the typelib if this project is exposed to COM 13 | [assembly: Guid("53c12419-2786-48d6-bb5d-e240f1d58f3b")] 14 | -------------------------------------------------------------------------------- /samples/SDL.SignalR.OracleMessageBus.Server/SDL.SignalR.OracleMessageBus.Server.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | Debug;Release 5 | Sdl.SignalR.OracleMessageBus.Server 6 | Sdl.SignalR.OracleMessageBus.Server 7 | Exe 8 | Sdl.SignalR.OracleMessageBus.Server 9 | Sdl.SignalR.OracleMessageBus.Server 10 | 11 | false 12 | false 13 | 14 | 15 | AnyCPU 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | AnyCPU 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | false 33 | 34 | 35 | Debug 36 | AnyCPU 37 | {E4ADC697-E387-4FBB-919C-6C4B2BF0FC19} 38 | Exe 39 | Properties 40 | 512 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | ..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.5\Microsoft.CSharp.dll 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /shared/Sdl.SignalR.OracleMessageBus.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RWS/SignalR-OracleMessageBus/b343c1b46f079a41e29b1e01b7bc6b0d2e3f11b7/shared/Sdl.SignalR.OracleMessageBus.snk -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/DataRecordExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using Oracle.ManagedDataAccess.Client; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus 6 | { 7 | internal static class DataRecordExtensions 8 | { 9 | public static byte[] GetBinary(this IDataRecord reader, int ordinalIndex) 10 | { 11 | var oracleDataReader = reader as OracleDataReader; 12 | if (oracleDataReader == null) 13 | { 14 | throw new NotSupportedException(); 15 | } 16 | 17 | return oracleDataReader.GetBinary(ordinalIndex); 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/DbCommandExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Threading.Tasks; 3 | using Oracle.ManagedDataAccess.Client; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus 6 | { 7 | internal static class DbCommandExtensions 8 | { 9 | public static Task ExecuteNonQueryAsync(this IDbCommand command) 10 | { 11 | var oracleCommand = command as OracleCommand; 12 | 13 | if (oracleCommand != null) 14 | { 15 | return oracleCommand.ExecuteNonQueryAsync(); 16 | } 17 | 18 | return TaskAsyncHelper.FromResult(command.ExecuteNonQuery()); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/DbConnectionExtension.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace Sdl.SignalR.OracleMessageBus 4 | { 5 | internal static class DbConnectionExtension 6 | { 7 | public static void CloseConnection(this IDbConnection connection) 8 | { 9 | if (connection.State != ConnectionState.Closed) 10 | { 11 | connection.Close(); 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/DbOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Data.Common; 5 | using System.Diagnostics; 6 | using System.Globalization; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace Sdl.SignalR.OracleMessageBus 11 | { 12 | internal class DbOperation : IDbOperation 13 | { 14 | public IList Parameters 15 | { 16 | get { return _parameters; } 17 | } 18 | 19 | protected TraceSource Trace { get; private set; } 20 | protected string ConnectionString { get; private set; } 21 | protected string CommandText { get; private set; } 22 | 23 | private readonly List _parameters = new List(); 24 | private readonly IDbProviderFactory _dbProviderFactory; 25 | 26 | public DbOperation(string connectionString, string commandText, TraceSource traceSource, IDbProviderFactory dbProviderFactory) 27 | { 28 | ConnectionString = connectionString; 29 | CommandText = commandText; 30 | Trace = traceSource; 31 | _dbProviderFactory = dbProviderFactory; 32 | } 33 | 34 | public DbOperation(string connectionString, string commandText, TraceSource traceSource, 35 | IDbProviderFactory dbProviderFactory, params IDataParameter[] parameters) : 36 | this(connectionString, commandText, traceSource, dbProviderFactory) 37 | { 38 | if (parameters != null) 39 | { 40 | _parameters.AddRange(parameters); 41 | } 42 | } 43 | 44 | public object ExecuteScalar() 45 | { 46 | return Execute(cmd => cmd.ExecuteScalar()); 47 | } 48 | 49 | public int ExecuteNonQuery() 50 | { 51 | return Execute(cmd => cmd.ExecuteNonQuery()); 52 | } 53 | 54 | public int ExecuteStoredProcedure() 55 | { 56 | return Execute(cmd => cmd.ExecuteNonQuery(), CommandType.StoredProcedure, false); 57 | } 58 | 59 | public Task ExecuteNonQueryAsync() 60 | { 61 | var tcs = new TaskCompletionSource(); 62 | Execute(cmd => cmd.ExecuteNonQueryAsync(), tcs, CommandType.StoredProcedure); 63 | return tcs.Task; 64 | } 65 | 66 | public int ExecuteReader(Action processRecord) 67 | { 68 | return ExecuteReader(processRecord, null); 69 | } 70 | 71 | #region [ private ] 72 | 73 | protected virtual int ExecuteReader(Action processRecord, Action commandAction) 74 | { 75 | return Execute(cmd => 76 | { 77 | if (commandAction != null) 78 | { 79 | commandAction(cmd); 80 | } 81 | 82 | var reader = cmd.ExecuteReader(); 83 | var count = 0; 84 | 85 | while (reader.Read()) 86 | { 87 | count++; 88 | processRecord(reader, this); 89 | } 90 | 91 | return count; 92 | }, CommandType.StoredProcedure); 93 | } 94 | 95 | private T Execute(Func commandFunc, CommandType commandType = CommandType.Text, bool cloneParameters = true) 96 | { 97 | T result = default(T); 98 | IDbConnection connection = null; 99 | 100 | try 101 | { 102 | connection = _dbProviderFactory.CreateConnection(); 103 | connection.ConnectionString = ConnectionString; 104 | var command = CreateCommand(connection, cloneParameters); 105 | command.CommandType = commandType; 106 | connection.Open(); 107 | TraceCommand(command); 108 | result = commandFunc(command); 109 | } 110 | catch (Exception ex) 111 | { 112 | Trace.TraceError("Error during connection open. Details: {0}", ex.ToString()); 113 | throw; 114 | } 115 | finally 116 | { 117 | if (connection != null) 118 | { 119 | connection.CloseConnection(); 120 | connection.Dispose(); 121 | } 122 | } 123 | 124 | return result; 125 | } 126 | 127 | private void Execute(Func> commandFunc, TaskCompletionSource tcs, CommandType commandType) 128 | { 129 | IDbConnection connection = null; 130 | try 131 | { 132 | connection = _dbProviderFactory.CreateConnection(); 133 | connection.ConnectionString = ConnectionString; 134 | var command = CreateCommand(connection); 135 | command.CommandType = commandType; 136 | 137 | connection.Open(); 138 | 139 | commandFunc(command) 140 | .Then(result => tcs.SetResult(result)) 141 | .Catch((exception, o) => 142 | { 143 | tcs.SetUnwrappedException(exception); 144 | 145 | }, Trace) 146 | .Finally(state => 147 | { 148 | var conn = (DbConnection)state; 149 | if (conn != null) 150 | { 151 | conn.CloseConnection(); 152 | conn.Dispose(); 153 | } 154 | }, connection); 155 | } 156 | catch (Exception ex) 157 | { 158 | Trace.TraceError("Error during connection open. Details: {0}", ex.ToString()); 159 | 160 | if (connection != null) 161 | { 162 | connection.CloseConnection(); 163 | connection.Dispose(); 164 | } 165 | 166 | throw; 167 | } 168 | } 169 | 170 | protected virtual IDbCommand CreateCommand(IDbConnection connection, bool cloneParameters = true) 171 | { 172 | var command = connection.CreateCommand(); 173 | command.CommandText = CommandText; 174 | 175 | if (Parameters != null && Parameters.Count > 0) 176 | { 177 | for (var i = 0; i < Parameters.Count; i++) 178 | { 179 | command.Parameters.Add(cloneParameters ? Parameters[i].Clone(_dbProviderFactory) : Parameters[i]); 180 | } 181 | } 182 | 183 | return command; 184 | } 185 | 186 | private void TraceCommand(IDbCommand command) 187 | { 188 | if (Trace.Switch.ShouldTrace(TraceEventType.Verbose)) 189 | { 190 | Trace.TraceVerbose("Created DbCommand: CommandType={0}, CommandText={1}, Parameters={2}", command.CommandType, command.CommandText, 191 | command.Parameters.Cast() 192 | .Aggregate(string.Empty, (msg, p) => string.Format(CultureInfo.InvariantCulture, "{0} [Name={1}, Value={2}]", msg, p.ParameterName, p.Value)) 193 | ); 194 | } 195 | } 196 | 197 | #endregion 198 | } 199 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/DbOperationFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Diagnostics; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal class DbOperationFactory : IDbOperationFactory 7 | { 8 | public IDbOperation CreateDbOperation(string connectionString, string commandText, TraceSource traceSource, 9 | IDbProviderFactory dbProviderFactory) 10 | { 11 | return new DbOperation(connectionString, commandText, traceSource, dbProviderFactory); 12 | } 13 | 14 | public IDbOperation CreateDbOperation(string connectionString, string commandText, 15 | TraceSource traceSource, 16 | IDbProviderFactory dbProviderFactory, 17 | params IDataParameter[] parameters) 18 | { 19 | return new DbOperation(connectionString, commandText, traceSource, dbProviderFactory, parameters); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/DbProviderFactoryAdapter.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Data.Common; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal class DbProviderFactoryAdapter : IDbProviderFactory 7 | { 8 | private readonly DbProviderFactory _dbProviderFactory; 9 | 10 | public DbProviderFactoryAdapter(DbProviderFactory dbProviderFactory) 11 | { 12 | _dbProviderFactory = dbProviderFactory; 13 | } 14 | 15 | public IDbConnection CreateConnection() 16 | { 17 | return _dbProviderFactory.CreateConnection(); 18 | } 19 | 20 | public IDataParameter CreateParameter() 21 | { 22 | return _dbProviderFactory.CreateParameter(); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/DbProviderFactoryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Data.Common; 2 | using Oracle.ManagedDataAccess.Client; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal static class DbProviderFactoryExtensions 7 | { 8 | public static IDbProviderFactory AsIDbProviderFactory(this DbProviderFactory dbProviderFactory, string connectionString) 9 | { 10 | using (OracleConnection oc = new OracleConnection(connectionString)) 11 | { 12 | return new DbProviderFactoryAdapter(DbProviderFactories.GetFactory(oc)); 13 | } 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/NotificationState.cs: -------------------------------------------------------------------------------- 1 | namespace Sdl.SignalR.OracleMessageBus 2 | { 3 | internal static class NotificationState 4 | { 5 | public const long Enabled = 0; 6 | public const long ProcessingUpdates = 1; 7 | public const long AwaitingNotification = 2; 8 | public const long NotificationReceived = 3; 9 | public const long Disabled = 4; 10 | } 11 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/ObservableDbOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.Threading; 7 | using Oracle.ManagedDataAccess.Client; 8 | 9 | namespace Sdl.SignalR.OracleMessageBus 10 | { 11 | internal class ObservableDbOperation : DbOperation, IObservableDbOperation, IDbBehavior 12 | { 13 | #region [ private fields ] 14 | 15 | private static readonly TimeSpan DependencyTimeout = TimeSpan.FromSeconds(60); 16 | 17 | private readonly Tuple[] _updateLoopRetryDelays = 18 | { 19 | Tuple.Create(0, 3), // 0ms x 3 20 | Tuple.Create(10, 3), // 10ms x 3 21 | Tuple.Create(50, 2), // 50ms x 2 22 | Tuple.Create(100, 2), // 100ms x 2 23 | Tuple.Create(200, 2), // 200ms x 2 24 | Tuple.Create(1000, 2), // 1000ms x 2 25 | Tuple.Create(1500, 2), // 1500ms x 2 26 | Tuple.Create(3000, 1) // 3000ms x 1 27 | }; 28 | 29 | private readonly object _stopLocker = new object(); 30 | private readonly ManualResetEventSlim _stopHandle = new ManualResetEventSlim(true); 31 | private volatile bool _disposing; 32 | private long _notificationState; 33 | 34 | private readonly bool _useOracleDependency; 35 | 36 | private readonly IDbBehavior _dbBehavior; 37 | private readonly IOracleDependencyManager _oracleDependencyManager; 38 | private readonly ISignalrDbDependencyFactory _signalrDbDependencyFactory; 39 | 40 | #endregion 41 | 42 | #region [ .ctors ] 43 | 44 | public ObservableDbOperation(string connectionString, string commandText, TraceSource traceSource, 45 | IDbBehavior dbBehavior, 46 | IDbProviderFactory dbProviderFactory, 47 | IOracleDependencyManager oracleDependencyManager, 48 | ISignalrDbDependencyFactory signalrDbDependencyFactory, 49 | bool useOracleDependency, 50 | params IDataParameter[] parameters) 51 | : base(connectionString, commandText, traceSource, dbProviderFactory, parameters) 52 | { 53 | _dbBehavior = dbBehavior ?? this; 54 | _oracleDependencyManager = oracleDependencyManager; 55 | _useOracleDependency = useOracleDependency; 56 | _signalrDbDependencyFactory = signalrDbDependencyFactory; 57 | } 58 | 59 | #endregion 60 | 61 | #region [ IObservableDbOperation implementation ] 62 | 63 | public event Action Queried; 64 | public event Action Changed; 65 | public event Action Faulted; 66 | 67 | public void ExecuteReaderWithUpdates(Action processRecord) 68 | { 69 | lock (_stopLocker) 70 | { 71 | if (_disposing) 72 | { 73 | return; 74 | } 75 | _stopHandle.Reset(); 76 | } 77 | 78 | bool useNotifications = _useOracleDependency; 79 | 80 | IList> delays = _dbBehavior.UpdateLoopRetryDelays; 81 | 82 | for (var i = 0; i < delays.Count; i++) 83 | { 84 | if (i == 0 && useNotifications) 85 | { 86 | // Reset the state to ProcessingUpdates if this is the start of the loop. 87 | // This should be safe to do here without Interlocked because the state is protected 88 | // in the other two cases using Interlocked, i.e. there should only be one instance of 89 | // this running at any point in time. 90 | _notificationState = NotificationState.ProcessingUpdates; 91 | } 92 | 93 | Tuple retry = delays[i]; 94 | int retryDelay = retry.Item1; 95 | int retryCount = retry.Item2; 96 | 97 | for (int j = 0; j < retryCount; j++) 98 | { 99 | if (_disposing) 100 | { 101 | Stop(null); 102 | return; 103 | } 104 | 105 | if (retryDelay > 0) 106 | { 107 | Trace.TraceVerbose("Waiting {0} ms before checking for messages again", retryDelay); 108 | 109 | Thread.Sleep(retryDelay); 110 | } 111 | 112 | var recordCount = 0; 113 | try 114 | { 115 | recordCount = ExecuteReader(processRecord); 116 | 117 | if (Queried != null) 118 | { 119 | Queried(); 120 | } 121 | } 122 | catch (Exception ex) 123 | { 124 | Trace.TraceError("Error in Oracle receive loop: {0}", ex); 125 | 126 | if (Faulted != null) 127 | { 128 | Faulted(ex); 129 | } 130 | } 131 | 132 | if (recordCount > 0) 133 | { 134 | Trace.TraceVerbose("{0} records received", recordCount); 135 | 136 | // We got records so start the retry loop again 137 | i = -1; 138 | break; 139 | } 140 | 141 | Trace.TraceVerbose("No records received"); 142 | 143 | var isLastRetry = i == delays.Count - 1 && j == retryCount - 1; 144 | 145 | if (isLastRetry) 146 | { 147 | // Last retry loop iteration 148 | if (!useNotifications) 149 | { 150 | // Last retry loop and we're not using notifications so just stay looping on the last retry delay 151 | j = j - 1; 152 | } 153 | else 154 | { 155 | #region [ use notifications ] 156 | // No records after all retries, set up a Oracle notification 157 | try 158 | { 159 | Trace.TraceVerbose("Setting up Oracle notification"); 160 | 161 | try 162 | { 163 | recordCount = ExecuteReader(processRecord, command => 164 | { 165 | _dbBehavior.AddOracleDependency(command, 166 | e => OracleDependency_OnChange(e, processRecord)); 167 | }); 168 | } 169 | catch (OracleException ex) 170 | { 171 | Trace.TraceError("Error while executing reader. Details: {0}", ex); 172 | 173 | if (ex.ErrorCode == 29972 ||// error creating change notification (not enough rights and stuff) 174 | (ex.Errors != null && ex.Errors.Count > 0 && ex.Errors[0].Number == 65131)) // the feature Continuous Query Notification is not supported in a pluggable database. 175 | { 176 | useNotifications = false; 177 | 178 | // Re-enter the loop on the last retry delay 179 | j = j - 1; 180 | } 181 | else 182 | { 183 | throw; 184 | } 185 | } 186 | 187 | if (Queried != null) 188 | { 189 | Queried(); 190 | } 191 | 192 | if (recordCount > 0) 193 | { 194 | Trace.TraceVerbose("Records were returned by the command that sets up the SQL notification, restarting the receive loop"); 195 | 196 | i = -1; 197 | break; // break the inner for loop 198 | } 199 | 200 | var previousState = Interlocked.CompareExchange(ref _notificationState, NotificationState.AwaitingNotification, 201 | NotificationState.ProcessingUpdates); 202 | 203 | if (previousState == NotificationState.AwaitingNotification) 204 | { 205 | Trace.TraceError("A Oracle notification was already running. Overlapping receive loops detected, this should never happen. BUG!"); 206 | 207 | return; 208 | } 209 | 210 | if (previousState == NotificationState.NotificationReceived) 211 | { 212 | // Failed to change _notificationState from ProcessingUpdates to AwaitingNotification, it was already NotificationReceived 213 | 214 | Trace.TraceVerbose("The Oracle notification fired before the receive loop returned, restarting the receive loop"); 215 | 216 | i = -1; 217 | break; // break the inner for loop 218 | } 219 | 220 | Trace.TraceVerbose("No records received while setting up Oracle notification"); 221 | 222 | // We're in a wait state for a notification now so check if we're disposing 223 | lock (_stopLocker) 224 | { 225 | if (_disposing) 226 | { 227 | _stopHandle.Set(); 228 | } 229 | } 230 | } 231 | catch (Exception ex) 232 | { 233 | Trace.TraceError("Error in SQL receive loop: {0}", ex); 234 | if (Faulted != null) 235 | { 236 | Faulted(ex); 237 | } 238 | 239 | // Re-enter the loop on the last retry delay 240 | j = j - 1; 241 | 242 | if (retryDelay > 0) 243 | { 244 | Trace.TraceVerbose("Waiting {0} ms before checking for messages again", retryDelay); 245 | 246 | Thread.Sleep(retryDelay); 247 | } 248 | } 249 | 250 | #endregion 251 | } 252 | } 253 | } 254 | } 255 | 256 | Trace.TraceVerbose("GetLastPayloadId loop exiting"); 257 | } 258 | 259 | public void Dispose() 260 | { 261 | lock (_stopLocker) 262 | { 263 | _disposing = true; 264 | } 265 | 266 | if (_notificationState != NotificationState.Disabled) 267 | { 268 | try 269 | { 270 | _oracleDependencyManager.RemoveRegistration(ConnectionString); 271 | } 272 | catch (Exception ex) 273 | { 274 | Trace.TraceError("Failed to stop oracle dependency: {0}", ex); 275 | } 276 | } 277 | 278 | if (Interlocked.Read(ref _notificationState) == NotificationState.ProcessingUpdates) 279 | { 280 | _stopHandle.Wait(); 281 | } 282 | 283 | _stopHandle.Dispose(); 284 | } 285 | 286 | #endregion 287 | 288 | #region [ IDbBehavior implementation ] 289 | 290 | public void AddOracleDependency(IDbCommand command, Action callback) 291 | { 292 | ISignalRDbDependency dependency = _signalrDbDependencyFactory.CreateDbDependency(command, true, (long)DependencyTimeout.TotalSeconds, false); 293 | dependency.OnChanged += (o, e) => callback(e); 294 | _oracleDependencyManager.RegisterDependency(dependency); 295 | } 296 | 297 | IList> IDbBehavior.UpdateLoopRetryDelays 298 | { 299 | get { return _updateLoopRetryDelays; } 300 | } 301 | 302 | #endregion 303 | 304 | #region [ helpers ] 305 | 306 | protected virtual void Stop(Exception ex) 307 | { 308 | if (ex != null) 309 | { 310 | if (Faulted != null) 311 | { 312 | Faulted(ex); 313 | } 314 | } 315 | 316 | if (_notificationState != NotificationState.Disabled) 317 | { 318 | try 319 | { 320 | Trace.TraceVerbose("Stopping Oracle notification listener"); 321 | _oracleDependencyManager.RemoveRegistration(ConnectionString); 322 | Trace.TraceVerbose("Oracle notification listener stopped"); 323 | } 324 | catch (Exception stopEx) 325 | { 326 | Trace.TraceError("Error occurred while stopping Oracle notification listener: {0}", stopEx); 327 | } 328 | } 329 | 330 | lock (_stopLocker) 331 | { 332 | if (_disposing) 333 | { 334 | _stopHandle.Set(); 335 | } 336 | } 337 | } 338 | 339 | protected virtual void OracleDependency_OnChange(ISignalRDbNotificationEventArgs e, 340 | Action processRecord) 341 | { 342 | Trace.TraceInformation("Oracle notification change fired"); 343 | 344 | lock (_stopLocker) 345 | { 346 | if (_disposing) 347 | { 348 | return; 349 | } 350 | } 351 | 352 | var previousState = Interlocked.CompareExchange(ref _notificationState, 353 | NotificationState.NotificationReceived, NotificationState.ProcessingUpdates); 354 | 355 | if (previousState == NotificationState.NotificationReceived) 356 | { 357 | Trace.TraceError("Overlapping Oracle change notifications received, this should never happen, BUG!"); 358 | 359 | return; 360 | } 361 | if (previousState == NotificationState.ProcessingUpdates) 362 | { 363 | // We're still in the original receive loop 364 | 365 | // New updates will be retrieved by the original reader thread 366 | Trace.TraceVerbose("Original reader processing is still in progress and will pick up the changes"); 367 | 368 | return; 369 | } 370 | 371 | // _notificationState wasn't ProcessingUpdates (likely AwaitingNotification) 372 | // Check notification args for issues 373 | if ((OracleNotificationType) e.NotificationType == OracleNotificationType.Change) 374 | { 375 | if ((OracleNotificationInfo) e.NotificationInfo == OracleNotificationInfo.Update) 376 | { 377 | Trace.TraceVerbose("Oracle notification details: Type={0}, Source={1}, Info={2}", 378 | e.NotificationType, e.NotificationSource, e.NotificationInfo); 379 | } 380 | else if ((OracleNotificationInfo) e.NotificationInfo == OracleNotificationInfo.End) 381 | { 382 | Trace.TraceVerbose("Oracle notification timed out"); 383 | } 384 | else 385 | { 386 | Trace.TraceError("Unexpected Oracle notification details: Type={0}, Source={1}, Info={2}", 387 | e.NotificationType, e.NotificationSource, e.NotificationInfo); 388 | 389 | if (Faulted != null) 390 | { 391 | Faulted(new OracleMessageBusException(string.Format(CultureInfo.InvariantCulture, 392 | "An unexpected SqlNotificationType was received. Details: Type={0}, Source={1}, Info={2}", 393 | e.NotificationType, e.NotificationSource, e.NotificationInfo))); 394 | } 395 | } 396 | } 397 | else if ((OracleNotificationType) e.NotificationType == OracleNotificationType.Subscribe) 398 | { 399 | Trace.TraceError("Oracle notification subscription error: Type={0}, Source={1}, Info={2}", 400 | e.NotificationType, e.NotificationSource, e.NotificationInfo); 401 | 402 | 403 | // Unknown subscription error, let's stop using query notifications 404 | _notificationState = NotificationState.Disabled; 405 | _oracleDependencyManager.RemoveRegistration(ConnectionString); 406 | } 407 | 408 | if (Changed != null) 409 | { 410 | Changed(); 411 | } 412 | } 413 | 414 | #endregion 415 | } 416 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/ObservableDbOperationFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Diagnostics; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal class ObservableDbOperationFactory : IObservableDbOperationFactory 7 | { 8 | private readonly IDbBehavior _dbBehavior; 9 | private readonly IOracleDependencyManager _oracleDependencyManager; 10 | private readonly ISignalrDbDependencyFactory _signalrDbDependencyFactory; 11 | 12 | public ObservableDbOperationFactory( 13 | IDbBehavior dbBehavior, 14 | IOracleDependencyManager oracleDependencyManager, 15 | ISignalrDbDependencyFactory signalrDbDependencyFactory) 16 | 17 | { 18 | _dbBehavior = dbBehavior; 19 | _oracleDependencyManager = oracleDependencyManager; 20 | _signalrDbDependencyFactory = signalrDbDependencyFactory; 21 | } 22 | 23 | public IObservableDbOperation ObservableDbOperation( 24 | string connectionString, 25 | string commandText, 26 | bool useOracleDependency, 27 | TraceSource traceSource, 28 | IDbProviderFactory dbProviderFactory, 29 | params IDataParameter[] parameters) 30 | { 31 | return new ObservableDbOperation(connectionString, commandText, traceSource, 32 | _dbBehavior, 33 | dbProviderFactory, 34 | _oracleDependencyManager, 35 | _signalrDbDependencyFactory, 36 | useOracleDependency, 37 | parameters); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/OracleDependencyManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using Oracle.ManagedDataAccess.Client; 7 | 8 | namespace Sdl.SignalR.OracleMessageBus 9 | { 10 | internal class OracleDependencyManager : IOracleDependencyManager 11 | { 12 | private static readonly List DependencyList = new List(); 13 | private static readonly object Sync = new object(); 14 | private readonly IDbProviderFactory _dbProviderFactory; 15 | private readonly TraceSource _traceSource; 16 | 17 | /* 18 | * How to create DbProviderFactory 19 | * OracleClientFactory.Instance.AsIDbProviderFactory().CreateConnection()) 20 | * 21 | */ 22 | public OracleDependencyManager(IDbProviderFactory dbProviderFactory, TraceSource traceSource) 23 | { 24 | _dbProviderFactory = dbProviderFactory; 25 | _traceSource = traceSource; 26 | } 27 | 28 | public void RemoveRegistration(string connectionString) 29 | { 30 | lock (Sync) 31 | { 32 | if (DependencyList.Any()) 33 | { 34 | using (IDbConnection nc = _dbProviderFactory.CreateConnection()) 35 | { 36 | nc.ConnectionString = connectionString; 37 | nc.Open(); 38 | 39 | foreach (ISignalRDbDependency dependency in DependencyList) 40 | { 41 | try 42 | { 43 | var oracleConnection = nc as OracleConnection; 44 | dependency.RemoveRegistration(oracleConnection); 45 | } 46 | catch (Exception ex) 47 | { 48 | _traceSource.TraceError( 49 | "Error during unregistering of oracle dependency. Details: {0}", ex.Message); 50 | } 51 | } 52 | 53 | nc.CloseConnection(); 54 | 55 | DependencyList.Clear(); 56 | } 57 | } 58 | } 59 | } 60 | 61 | public void RegisterDependency(ISignalRDbDependency dep) 62 | { 63 | lock (Sync) 64 | { 65 | DependencyList.Add(dep); 66 | } 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/OracleMessageBusException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sdl.SignalR.OracleMessageBus 4 | { 5 | [Serializable] 6 | public class OracleMessageBusException : Exception 7 | { 8 | public OracleMessageBusException(string message) 9 | : base(message) 10 | { 11 | 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/SignalRDbNotificationEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Oracle.ManagedDataAccess.Client; 2 | 3 | namespace Sdl.SignalR.OracleMessageBus 4 | { 5 | internal class SignalRDbNotificationEventArgs : ISignalRDbNotificationEventArgs 6 | { 7 | public int NotificationType { get { return (int) _e.Type; } } 8 | public int NotificationInfo { get { return (int) _e.Info; } } 9 | public int NotificationSource { get { return (int) _e.Source; } } 10 | 11 | private readonly OracleNotificationEventArgs _e; 12 | 13 | public SignalRDbNotificationEventArgs(OracleNotificationEventArgs e) 14 | { 15 | _e = e; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/SignalrDbDependency.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Oracle.ManagedDataAccess.Client; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal class SignalrDbDependency : ISignalRDbDependency 7 | { 8 | private OracleDependency _dep; 9 | public SignalrDbDependency(OracleDependency dep) 10 | { 11 | _dep = dep; 12 | 13 | _dep.OnChange += (sender, args) => 14 | { 15 | if (OnChanged != null) 16 | { 17 | OnChanged(sender, new SignalRDbNotificationEventArgs(args)); 18 | } 19 | }; 20 | } 21 | 22 | public event EventHandler OnChanged; 23 | 24 | public void RemoveRegistration(OracleConnection conn) 25 | { 26 | _dep.RemoveRegistration(conn); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DbOperation/SignalrDbDependencyFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using Oracle.ManagedDataAccess.Client; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus 6 | { 7 | internal class SignalrDbDependencyFactory : ISignalrDbDependencyFactory 8 | { 9 | public ISignalRDbDependency CreateDbDependency(IDbCommand command, bool isNotifiedOnce, long timeoutInSec, bool isPersistent) 10 | { 11 | var oracleCommand = command as OracleCommand; 12 | if (oracleCommand == null) 13 | { 14 | throw new NotSupportedException(); 15 | } 16 | 17 | OracleDependency od = new OracleDependency(oracleCommand, isNotifiedOnce, timeoutInSec, isPersistent); 18 | return new SignalrDbDependency(od); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/DependencyResolverExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNet.SignalR.Messaging; 3 | using Microsoft.AspNet.SignalR; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus 6 | { 7 | public static class DependencyResolverExtensions 8 | { 9 | /// 10 | /// Use Oracle as the messaging backplane for scaling out of ASP.NET SignalR applications in a web farm. 11 | /// 12 | /// The dependency resolver. 13 | /// The connection string to the Oracle Server. 14 | /// Flag to determine if Oracle dependency should be used. 15 | /// The dependency port of the Oracle server. 16 | /// The dependency resolver. 17 | public static IDependencyResolver UseOracle(this IDependencyResolver resolver, string connectionString, bool useOracleDependency = false, int? oracleDependencyPort = null) 18 | { 19 | var configuration = new OracleScaleoutConfiguration(connectionString, useOracleDependency, oracleDependencyPort); 20 | 21 | return UseOracle(resolver, configuration); 22 | } 23 | 24 | /// 25 | /// Use Redis as the messaging backplane for scaling out of ASP.NET SignalR applications in a web farm. 26 | /// 27 | /// The dependency resolver 28 | /// The Redis scale-out configuration options. 29 | /// The dependency resolver. 30 | public static IDependencyResolver UseOracle(this IDependencyResolver resolver, OracleScaleoutConfiguration configuration) 31 | { 32 | var bus = new Lazy(() => new OracleMessageBus(resolver, configuration)); 33 | resolver.Register(typeof(IMessageBus), () => bus.Value); 34 | 35 | return resolver; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Helpers/TaskAsyncHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Globalization; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace Sdl.SignalR.OracleMessageBus 9 | { 10 | internal static class TaskAsyncHelper 11 | { 12 | private static readonly Task EmptyTask = MakeTask(null); 13 | 14 | private static Task MakeTask(T value) 15 | { 16 | return FromResult(value); 17 | } 18 | 19 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 20 | public static Task Empty 21 | { 22 | get 23 | { 24 | return EmptyTask; 25 | } 26 | } 27 | 28 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 29 | public static TTask Catch(this TTask task, Action handler, object state, TraceSource traceSource = null) where TTask : Task 30 | { 31 | if (task != null && task.Status != TaskStatus.RanToCompletion) 32 | { 33 | if (task.Status == TaskStatus.Faulted) 34 | { 35 | ExecuteOnFaulted(handler, state, task.Exception, traceSource); 36 | } 37 | else 38 | { 39 | AttachFaultedContinuation(task, handler, state, traceSource); 40 | } 41 | } 42 | 43 | return task; 44 | } 45 | 46 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 47 | private static void AttachFaultedContinuation(TTask task, Action handler, object state, TraceSource traceSource) where TTask : Task 48 | { 49 | task.ContinueWithPreservedCulture(innerTask => ExecuteOnFaulted(handler, state, innerTask.Exception, traceSource), 50 | TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously); 51 | } 52 | 53 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 54 | private static void ExecuteOnFaulted(Action handler, object state, AggregateException exception, TraceSource traceSource) 55 | { 56 | // Observe Exception 57 | if (traceSource != null) 58 | { 59 | traceSource.TraceEvent(TraceEventType.Warning, 0, "Exception thrown by Task: {0}", exception); 60 | } 61 | handler(exception, state); 62 | } 63 | 64 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", 65 | Justification = "This is a shared file")] 66 | public static TTask Catch(this TTask task, Action handler, 67 | TraceSource traceSource = null) where TTask : Task 68 | { 69 | return task.Catch((ex, state) => ((Action) state).Invoke(ex), 70 | handler, 71 | traceSource 72 | ); 73 | } 74 | 75 | // Then extensions 76 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 77 | public static Task Then(this Task task, Action successor) 78 | { 79 | switch (task.Status) 80 | { 81 | case TaskStatus.Faulted: 82 | case TaskStatus.Canceled: 83 | return task; 84 | 85 | case TaskStatus.RanToCompletion: 86 | return FromMethod(successor); 87 | 88 | default: 89 | return RunTask(task, successor); 90 | } 91 | } 92 | 93 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 94 | public static Task Then(this Task task, Action successor) 95 | { 96 | switch (task.Status) 97 | { 98 | case TaskStatus.Faulted: 99 | case TaskStatus.Canceled: 100 | return task; 101 | 102 | case TaskStatus.RanToCompletion: 103 | return FromMethod(successor, task.Result); 104 | 105 | default: 106 | return TaskRunners.RunTask(task, successor); 107 | } 108 | } 109 | 110 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are flowed to the caller")] 111 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 112 | public static Task Finally(this Task task, Action next, object state) 113 | { 114 | try 115 | { 116 | switch (task.Status) 117 | { 118 | case TaskStatus.Faulted: 119 | case TaskStatus.Canceled: 120 | next(state); 121 | return task; 122 | case TaskStatus.RanToCompletion: 123 | return FromMethod(next, state); 124 | 125 | default: 126 | return RunTaskSynchronously(task, next, state, onlyOnSuccess: false); 127 | } 128 | } 129 | catch (Exception ex) 130 | { 131 | return FromError(ex); 132 | } 133 | } 134 | 135 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 136 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are set in a tcs")] 137 | public static Task FromMethod(Action func) 138 | { 139 | try 140 | { 141 | func(); 142 | return Empty; 143 | } 144 | catch (Exception ex) 145 | { 146 | return FromError(ex); 147 | } 148 | } 149 | 150 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 151 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are set in a tcs")] 152 | public static Task FromMethod(Action func, T1 arg) 153 | { 154 | try 155 | { 156 | func(arg); 157 | return Empty; 158 | } 159 | catch (Exception ex) 160 | { 161 | return FromError(ex); 162 | } 163 | } 164 | 165 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 166 | public static Task FromResult(T value) 167 | { 168 | var tcs = new TaskCompletionSource(); 169 | tcs.SetResult(value); 170 | return tcs.Task; 171 | } 172 | 173 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 174 | internal static Task FromError(Exception e) 175 | { 176 | return FromError(e); 177 | } 178 | 179 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 180 | internal static Task FromError(Exception e) 181 | { 182 | var tcs = new TaskCompletionSource(); 183 | tcs.SetUnwrappedException(e); 184 | return tcs.Task; 185 | } 186 | 187 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 188 | internal static void SetUnwrappedException(this TaskCompletionSource tcs, Exception e) 189 | { 190 | var aggregateException = e as AggregateException; 191 | if (aggregateException != null) 192 | { 193 | tcs.SetException(aggregateException.InnerExceptions); 194 | } 195 | else 196 | { 197 | tcs.SetException(e); 198 | } 199 | } 200 | 201 | internal struct CulturePair 202 | { 203 | public CultureInfo Culture; 204 | public CultureInfo UICulture; 205 | } 206 | 207 | internal static CulturePair SaveCulture() 208 | { 209 | return new CulturePair 210 | { 211 | Culture = Thread.CurrentThread.CurrentCulture, 212 | UICulture = Thread.CurrentThread.CurrentUICulture 213 | }; 214 | } 215 | 216 | internal static TResult RunWithPreservedCulture(CulturePair preservedCulture, Func func, T1 arg1, T2 arg2) 217 | { 218 | var replacedCulture = SaveCulture(); 219 | try 220 | { 221 | Thread.CurrentThread.CurrentCulture = preservedCulture.Culture; 222 | Thread.CurrentThread.CurrentUICulture = preservedCulture.UICulture; 223 | return func(arg1, arg2); 224 | } 225 | finally 226 | { 227 | Thread.CurrentThread.CurrentCulture = replacedCulture.Culture; 228 | Thread.CurrentThread.CurrentUICulture = replacedCulture.UICulture; 229 | } 230 | } 231 | 232 | internal static void RunWithPreservedCulture(CulturePair preservedCulture, Action action, T arg) 233 | { 234 | RunWithPreservedCulture(preservedCulture, (f, state) => 235 | { 236 | f(state); 237 | return (object)null; 238 | }, 239 | action, arg); 240 | } 241 | 242 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 243 | internal static void RunWithPreservedCulture(CulturePair preservedCulture, Action action) 244 | { 245 | RunWithPreservedCulture(preservedCulture, f => f(), action); 246 | } 247 | 248 | internal static Task ContinueWithPreservedCulture(this Task task, Action continuationAction, TaskContinuationOptions continuationOptions) 249 | { 250 | var preservedCulture = SaveCulture(); 251 | return task.ContinueWith(t => RunWithPreservedCulture(preservedCulture, continuationAction, t), continuationOptions); 252 | } 253 | 254 | internal static Task ContinueWithPreservedCulture(this Task task, Action> continuationAction, TaskContinuationOptions continuationOptions) 255 | { 256 | var preservedCulture = SaveCulture(); 257 | return task.ContinueWith(t => RunWithPreservedCulture(preservedCulture, continuationAction, t), continuationOptions); 258 | } 259 | 260 | internal static Task ContinueWithPreservedCulture(this Task task, Action continuationAction) 261 | { 262 | return task.ContinueWithPreservedCulture(continuationAction, TaskContinuationOptions.None); 263 | } 264 | 265 | internal static Task ContinueWithPreservedCulture(this Task task, Action> continuationAction) 266 | { 267 | return task.ContinueWithPreservedCulture(continuationAction, TaskContinuationOptions.None); 268 | } 269 | 270 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are set in a tcs")] 271 | private static Task RunTask(Task task, Action successor) 272 | { 273 | var tcs = new TaskCompletionSource(); 274 | task.ContinueWithPreservedCulture(t => 275 | { 276 | if (t.IsFaulted) 277 | { 278 | tcs.SetUnwrappedException(t.Exception); 279 | } 280 | else if (t.IsCanceled) 281 | { 282 | tcs.SetCanceled(); 283 | } 284 | else 285 | { 286 | try 287 | { 288 | successor(); 289 | tcs.SetResult(null); 290 | } 291 | catch (Exception ex) 292 | { 293 | tcs.SetUnwrappedException(ex); 294 | } 295 | } 296 | }); 297 | 298 | return tcs.Task; 299 | } 300 | 301 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are set in a tcs")] 302 | [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "This is a shared file")] 303 | private static Task RunTaskSynchronously(Task task, Action next, object state, bool onlyOnSuccess = true) 304 | { 305 | var tcs = new TaskCompletionSource(); 306 | task.ContinueWithPreservedCulture(t => 307 | { 308 | try 309 | { 310 | if (t.IsFaulted) 311 | { 312 | if (!onlyOnSuccess) 313 | { 314 | next(state); 315 | } 316 | 317 | tcs.SetUnwrappedException(t.Exception); 318 | } 319 | else if (t.IsCanceled) 320 | { 321 | if (!onlyOnSuccess) 322 | { 323 | next(state); 324 | } 325 | 326 | tcs.SetCanceled(); 327 | } 328 | else 329 | { 330 | next(state); 331 | tcs.SetResult(null); 332 | } 333 | } 334 | catch (Exception ex) 335 | { 336 | tcs.SetUnwrappedException(ex); 337 | } 338 | }, 339 | TaskContinuationOptions.ExecuteSynchronously); 340 | 341 | return tcs.Task; 342 | } 343 | 344 | private static class TaskRunners 345 | { 346 | [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "Exceptions are set in a tcs")] 347 | internal static Task RunTask(Task task, Action successor) 348 | { 349 | var tcs = new TaskCompletionSource(); 350 | task.ContinueWithPreservedCulture(t => 351 | { 352 | if (t.IsFaulted) 353 | { 354 | tcs.SetUnwrappedException(t.Exception); 355 | } 356 | else if (t.IsCanceled) 357 | { 358 | tcs.SetCanceled(); 359 | } 360 | else 361 | { 362 | try 363 | { 364 | successor(t.Result); 365 | tcs.SetResult(null); 366 | } 367 | catch (Exception ex) 368 | { 369 | tcs.SetUnwrappedException(ex); 370 | } 371 | } 372 | }); 373 | 374 | return tcs.Task; 375 | } 376 | } 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IDataParameterExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using Oracle.ManagedDataAccess.Client; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal static class DataParameterExtensions 7 | { 8 | public static IDataParameter Clone(this IDataParameter sourceParameter, IDbProviderFactory dbProviderFactory) 9 | { 10 | IDataParameter newParameter = dbProviderFactory.CreateParameter(); 11 | 12 | newParameter.ParameterName = sourceParameter.ParameterName; 13 | newParameter.DbType = sourceParameter.DbType; 14 | newParameter.Value = sourceParameter.Value; 15 | newParameter.Direction = sourceParameter.Direction; 16 | 17 | var oracleSourceParameter = sourceParameter as OracleParameter; 18 | var oracleNewParameter = newParameter as OracleParameter; 19 | if (oracleNewParameter != null && oracleSourceParameter != null) 20 | { 21 | oracleNewParameter.OracleDbType = oracleSourceParameter.OracleDbType; 22 | } 23 | 24 | return newParameter; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IDbBehavior.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus 6 | { 7 | internal interface IDbBehavior 8 | { 9 | IList> UpdateLoopRetryDelays { get; } 10 | void AddOracleDependency(IDbCommand command, Action callback); 11 | } 12 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IDbOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Threading.Tasks; 5 | 6 | namespace Sdl.SignalR.OracleMessageBus 7 | { 8 | internal interface IDbOperation 9 | { 10 | object ExecuteScalar(); 11 | int ExecuteNonQuery(); 12 | int ExecuteStoredProcedure(); 13 | Task ExecuteNonQueryAsync(); 14 | IList Parameters { get; } 15 | int ExecuteReader(Action processRecord); 16 | } 17 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IDbOperationFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Diagnostics; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal interface IDbOperationFactory 7 | { 8 | IDbOperation CreateDbOperation(string connectionString, string commandText, TraceSource traceSource, 9 | IDbProviderFactory dbProviderFactory); 10 | 11 | IDbOperation CreateDbOperation(string connectionString, string commandText, TraceSource traceSource, 12 | IDbProviderFactory dbProviderFactory, params IDataParameter[] parameters); 13 | } 14 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IDbProviderFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace Sdl.SignalR.OracleMessageBus 4 | { 5 | internal interface IDbProviderFactory 6 | { 7 | IDbConnection CreateConnection(); 8 | IDataParameter CreateParameter(); 9 | } 10 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IObservableDbOperation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal interface IObservableDbOperation : IDbOperation, IDisposable 7 | { 8 | event Action Queried; 9 | event Action Changed; 10 | event Action Faulted; 11 | void ExecuteReaderWithUpdates(Action processRecord); 12 | } 13 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IObservableDbOperationFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | using System.Diagnostics; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal interface IObservableDbOperationFactory 7 | { 8 | IObservableDbOperation ObservableDbOperation( 9 | string connectionString, 10 | string commandText, 11 | bool useOracleDependency, 12 | TraceSource traceSource, 13 | IDbProviderFactory dbProviderFactory, 14 | params IDataParameter[] parameters); 15 | } 16 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IOracleDependencyManager.cs: -------------------------------------------------------------------------------- 1 | namespace Sdl.SignalR.OracleMessageBus 2 | { 3 | internal interface IOracleDependencyManager 4 | { 5 | void RemoveRegistration(string connectionString); 6 | void RegisterDependency(ISignalRDbDependency dependency); 7 | } 8 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IOracleReceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNet.SignalR.Messaging; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal interface IOracleReceiver : IDisposable 7 | { 8 | event Action Queried; 9 | event Action Received; 10 | event Action Faulted; 11 | void GetLastPayloadId(); 12 | void StartReceivingUpdatesFromDb(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/IOracleSender.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNet.SignalR.Messaging; 4 | 5 | namespace Sdl.SignalR.OracleMessageBus 6 | { 7 | internal interface IOracleSender 8 | { 9 | Task Send(IList messages); 10 | } 11 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/ISignalRDbDependency.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Oracle.ManagedDataAccess.Client; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | internal interface ISignalRDbDependency 7 | { 8 | event EventHandler OnChanged; 9 | void RemoveRegistration(OracleConnection conn); 10 | } 11 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/ISignalRDbNotificationEventArgs.cs: -------------------------------------------------------------------------------- 1 | namespace Sdl.SignalR.OracleMessageBus 2 | { 3 | internal interface ISignalRDbNotificationEventArgs 4 | { 5 | int NotificationType { get; } 6 | int NotificationInfo { get; } 7 | int NotificationSource {get; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Interfaces/ISignalrDbDependencyFactory.cs: -------------------------------------------------------------------------------- 1 | using System.Data; 2 | 3 | namespace Sdl.SignalR.OracleMessageBus 4 | { 5 | internal interface ISignalrDbDependencyFactory 6 | { 7 | ISignalRDbDependency CreateDbDependency(IDbCommand command, bool isNotifiedOnce, long timeoutInSec, 8 | bool isPersistent); 9 | } 10 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/MessageWrapper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNet.SignalR.Messaging; 2 | 3 | namespace Sdl.SignalR.OracleMessageBus 4 | { 5 | internal class MessageWrapper 6 | { 7 | public MessageWrapper(long id, ScaleoutMessage message) 8 | { 9 | Id = id; 10 | Message = message; 11 | } 12 | 13 | public long Id { get; set; } 14 | public ScaleoutMessage Message { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/OracleInstaller.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.IO; 3 | using System.Transactions; 4 | using IsolationLevel = System.Transactions.IsolationLevel; 5 | using System.Data.Common; 6 | 7 | namespace Sdl.SignalR.OracleMessageBus 8 | { 9 | internal class OracleInstaller 10 | { 11 | private const string InstallScript = "install.sql"; 12 | private readonly string _connectionString; 13 | private readonly TraceSource _trace; 14 | private readonly IDbProviderFactory _dbProviderFactory; 15 | private readonly IDbOperationFactory _dbOperationFactory; 16 | 17 | public OracleInstaller(string connectionString, TraceSource traceSource, IDbProviderFactory dbProviderFactory, 18 | IDbOperationFactory dbOperationFactory) 19 | { 20 | _connectionString = connectionString; 21 | _trace = traceSource; 22 | _dbProviderFactory = dbProviderFactory; 23 | _dbOperationFactory = dbOperationFactory; 24 | } 25 | 26 | public void Install() 27 | { 28 | _trace.TraceInformation("Start installing SignalR Oracle objects"); 29 | 30 | string script = null; 31 | using (var resourceStream = GetType().Assembly.GetManifestResourceStream(string.Concat(GetType().Namespace, ".", InstallScript))) 32 | { 33 | var reader = new StreamReader(resourceStream); 34 | script = reader.ReadToEnd(); 35 | } 36 | 37 | DbConnectionStringBuilder builder = new DbConnectionStringBuilder(); 38 | builder.ConnectionString = _connectionString; 39 | string userId = builder["User Id"].ToString(); 40 | script = script.Replace("TABLESPACE &TBLSPC_TDS_TABLES", string.Empty) 41 | .Replace("&DBOWNER", userId.ToUpperInvariant()); 42 | 43 | using ( 44 | new TransactionScope(TransactionScopeOption.Required, 45 | new TransactionOptions {IsolationLevel = IsolationLevel.Serializable})) 46 | { 47 | var command = _dbOperationFactory.CreateDbOperation(_connectionString, script, _trace, _dbProviderFactory); 48 | command.ExecuteNonQuery(); 49 | } 50 | 51 | _trace.TraceInformation("SignalR Oracle objects installed"); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/OracleMessageBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNet.SignalR; 7 | using Microsoft.AspNet.SignalR.Messaging; 8 | using Microsoft.AspNet.SignalR.Tracing; 9 | using Oracle.ManagedDataAccess.Client; 10 | 11 | namespace Sdl.SignalR.OracleMessageBus 12 | { 13 | public class OracleMessageBus : ScaleoutMessageBus 14 | { 15 | private readonly string _connectionString; 16 | private readonly TraceSource _trace; 17 | private readonly IDbProviderFactory _dbProviderFactory; 18 | private readonly List _streams = new List(); 19 | private readonly bool _useOracleDependency; 20 | private readonly IDbOperationFactory _dbOperationFactory; 21 | private readonly IObservableDbOperationFactory _observableDbOperationFactory; 22 | 23 | private bool _retryConnecting = true; 24 | 25 | private readonly CancellationTokenSource _cts = new CancellationTokenSource(); 26 | 27 | /// 28 | /// Initializes a new instance of the class. 29 | /// 30 | /// The resolver to use. 31 | /// The Oracle scale-out configuration options. 32 | public OracleMessageBus(IDependencyResolver resolver, 33 | OracleScaleoutConfiguration configuration) 34 | : base(resolver, configuration) 35 | { 36 | if (configuration == null) 37 | { 38 | throw new ArgumentNullException("configuration"); 39 | } 40 | 41 | if (configuration.UseOracleDependency && configuration.OracleDependencyPort.HasValue) 42 | { 43 | OracleDependency.Port = configuration.OracleDependencyPort.Value; 44 | } 45 | 46 | _connectionString = configuration.ConnectionString; 47 | _dbProviderFactory = OracleClientFactory.Instance.AsIDbProviderFactory(configuration.ConnectionString); 48 | 49 | var traceManager = resolver.Resolve(); 50 | _trace = traceManager[typeof(OracleMessageBus).Name]; 51 | 52 | _dbOperationFactory = new DbOperationFactory(); 53 | _observableDbOperationFactory = new ObservableDbOperationFactory(null, new OracleDependencyManager(_dbProviderFactory, _trace), 54 | new SignalrDbDependencyFactory()); 55 | _useOracleDependency = configuration.UseOracleDependency; 56 | 57 | Task.Run(() => Initialize()); 58 | } 59 | 60 | /// 61 | /// Sends messages to the backplane. 62 | /// 63 | protected override Task Send(int streamIndex, IList messages) 64 | { 65 | return _streams[streamIndex].Send(messages); 66 | } 67 | 68 | protected override void Dispose(bool disposing) 69 | { 70 | _trace.TraceInformation("Oracle message bus disposing, disposing receiver"); 71 | _cts.Cancel(); 72 | for (var i = 0; i < _streams.Count; i++) 73 | { 74 | _streams[i].Dispose(); 75 | } 76 | 77 | base.Dispose(disposing); 78 | } 79 | 80 | private void Initialize() 81 | { 82 | _trace.TraceInformation("Oracle message bus initializing"); 83 | 84 | try 85 | { 86 | var installer = new OracleInstaller(_connectionString, _trace, _dbProviderFactory, _dbOperationFactory); 87 | installer.Install(); 88 | } 89 | catch (Exception ex) 90 | { 91 | for (int i = 0; i < StreamCount; i++) 92 | { 93 | OnError(0, ex); 94 | } 95 | 96 | _trace.TraceError("Error trying to install Oracle objects: {0}", ex); 97 | } 98 | 99 | for (int i = 0; i < StreamCount; i++) 100 | { 101 | int streamIndex = i; 102 | var stream = new OracleStream(streamIndex, _connectionString, _useOracleDependency, _trace, 103 | _dbProviderFactory, _dbOperationFactory, _observableDbOperationFactory); 104 | 105 | _streams.Add(stream); 106 | 107 | stream.Queried += () => Open(streamIndex); 108 | stream.Faulted += ex => OnError(streamIndex, ex); 109 | stream.Received += (id, message) => OnReceived(streamIndex, id, message); 110 | 111 | StartReceiving(streamIndex); 112 | } 113 | } 114 | 115 | private void StartReceiving(int streamIndex) 116 | { 117 | Task.Run(() => 118 | { 119 | if (!_retryConnecting) 120 | { 121 | return; 122 | } 123 | 124 | _streams[streamIndex].GetLastPayloadId(); 125 | }) 126 | .Then(() => 127 | { 128 | Open(streamIndex); 129 | }) 130 | .ContinueWith(task => 131 | { 132 | if (task.IsCompleted && !task.IsFaulted) 133 | { 134 | _streams[streamIndex].StartReceivingUpdatesFromDb(); 135 | } 136 | else 137 | { 138 | task.Catch(ex => 139 | { 140 | OracleException oracleException = ex.InnerException as OracleException; 141 | // In case of "ORA-01017: invalid username/password; logon denied" we try ones. 142 | // In case of another error we keep trying to start polling database. 143 | if (oracleException != null && oracleException.Errors != null && oracleException.Errors.Count > 0 && oracleException.Errors[0].Number == 1017) 144 | { 145 | _retryConnecting = false; 146 | } 147 | 148 | OnError(streamIndex, ex); 149 | Task.Delay(TimeSpan.FromSeconds(2)).Wait(); 150 | StartReceiving(streamIndex); 151 | }); 152 | } 153 | }, TaskContinuationOptions.LongRunning); 154 | } 155 | } 156 | } -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/OraclePayload.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using Microsoft.AspNet.SignalR.Messaging; 5 | using Oracle.ManagedDataAccess.Client; 6 | 7 | namespace Sdl.SignalR.OracleMessageBus 8 | { 9 | internal static class OraclePayload 10 | { 11 | public static byte[] ToBytes(IList messages) 12 | { 13 | if (messages == null) 14 | { 15 | throw new ArgumentNullException("messages"); 16 | } 17 | 18 | var message = new ScaleoutMessage(messages); 19 | return message.ToBytes(); 20 | } 21 | 22 | public static ScaleoutMessage FromBytes(IDataRecord record) 23 | { 24 | var message = ScaleoutMessage.FromBytes(GetBinary(record, 1)); 25 | return message; 26 | } 27 | 28 | private static byte[] GetBinary(IDataRecord reader, int ordinalIndex) 29 | { 30 | var oracleReader = reader as OracleDataReader; 31 | if (oracleReader == null) 32 | { 33 | throw new NotSupportedException(); 34 | } 35 | 36 | return oracleReader.GetOracleBinary(ordinalIndex).Value; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/OracleReceiver.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Diagnostics; 4 | using Microsoft.AspNet.SignalR.Messaging; 5 | using Oracle.ManagedDataAccess.Client; 6 | 7 | namespace Sdl.SignalR.OracleMessageBus 8 | { 9 | internal class OracleReceiver : IOracleReceiver 10 | { 11 | public event Action Queried; 12 | public event Action Received; 13 | public event Action Faulted; 14 | 15 | private readonly string _connectionString; 16 | private readonly TraceSource _traceSource; 17 | private readonly IDbProviderFactory _dbProviderFactory; 18 | private readonly IDbOperationFactory _dbOperationFactory; 19 | private readonly IObservableDbOperationFactory _observableDbOperationFactory; 20 | 21 | private string _maxIdSql = "SIGNALR.PAYLOADID_GET"; 22 | private string _selectSql = "SIGNALR.PAYLOAD_READ"; 23 | private IObservableDbOperation _observableDbOperation; 24 | private volatile bool _disposed; 25 | private long? _lastPayloadId; 26 | private readonly bool _useOracleDependency; 27 | 28 | public OracleReceiver(string connectionString, 29 | bool useOracleDependency, 30 | TraceSource traceSource, 31 | IDbProviderFactory dbProviderFactory, 32 | IDbOperationFactory dbOperationFactory, 33 | IObservableDbOperationFactory observableDbOperationFactory) 34 | { 35 | _connectionString = connectionString; 36 | _useOracleDependency = useOracleDependency; 37 | _traceSource = traceSource; 38 | _dbProviderFactory = dbProviderFactory; 39 | _dbOperationFactory = dbOperationFactory; 40 | _observableDbOperationFactory = observableDbOperationFactory; 41 | } 42 | 43 | public void Dispose() 44 | { 45 | lock (this) 46 | { 47 | if (_observableDbOperation != null) 48 | { 49 | _observableDbOperation.Dispose(); 50 | } 51 | 52 | _disposed = true; 53 | _traceSource.TraceInformation("OracleReceiver disposed"); 54 | } 55 | } 56 | 57 | public void GetLastPayloadId() 58 | { 59 | if (!_lastPayloadId.HasValue) 60 | { 61 | IDataParameter parameter = _dbProviderFactory.CreateParameter(); 62 | parameter.DbType = DbType.Int64; 63 | parameter.Direction = ParameterDirection.InputOutput; 64 | parameter.ParameterName = "oPayloadId"; 65 | 66 | IDbOperation lastPayloadIdOperation = _dbOperationFactory.CreateDbOperation(_connectionString, _maxIdSql, 67 | _traceSource, _dbProviderFactory, parameter); 68 | 69 | try 70 | { 71 | lastPayloadIdOperation.ExecuteStoredProcedure(); 72 | _lastPayloadId = (long?) parameter.Value; 73 | 74 | if (Queried != null) 75 | { 76 | Queried(); 77 | } 78 | 79 | _traceSource.TraceVerbose("OracleReceiver started, initial payload id={0}", _lastPayloadId); 80 | } 81 | catch (Exception ex) 82 | { 83 | if (Faulted != null) 84 | { 85 | Faulted(ex); 86 | } 87 | 88 | _traceSource.TraceError("OracleReceiver error starting: {0}", ex); 89 | 90 | throw; 91 | } 92 | } 93 | } 94 | 95 | public void StartReceivingUpdatesFromDb() 96 | { 97 | lock (this) 98 | { 99 | if (_disposed) 100 | { 101 | return; 102 | } 103 | 104 | IDataParameter parameter = _dbProviderFactory.CreateParameter(); 105 | parameter.ParameterName = "iPayloadId"; 106 | parameter.Value = _lastPayloadId; 107 | parameter.DbType = DbType.Decimal; 108 | parameter.Direction = ParameterDirection.Input; 109 | 110 | OracleParameter resultSetParameter = _dbProviderFactory.CreateParameter() as OracleParameter; 111 | if (resultSetParameter != null) 112 | { 113 | resultSetParameter.ParameterName = "oRefCur"; 114 | resultSetParameter.OracleDbType = OracleDbType.RefCursor; 115 | resultSetParameter.Direction = ParameterDirection.Output; 116 | } 117 | 118 | _observableDbOperation = _observableDbOperationFactory.ObservableDbOperation(_connectionString, 119 | _selectSql, _useOracleDependency, _traceSource, _dbProviderFactory, parameter, resultSetParameter); 120 | } 121 | 122 | _observableDbOperation.Queried += () => 123 | { 124 | if (Queried != null) 125 | { 126 | Queried(); 127 | } 128 | }; 129 | 130 | _observableDbOperation.Faulted += ex => 131 | { 132 | if (Faulted != null) 133 | { 134 | Faulted(ex); 135 | } 136 | }; 137 | 138 | _observableDbOperation.Changed += () => 139 | { 140 | _traceSource.TraceInformation("Starting receive loop again to process updates"); 141 | _observableDbOperation.ExecuteReaderWithUpdates(ProcessRecord); 142 | }; 143 | 144 | _traceSource.TraceVerbose("Executing receive reader, initial payload ID parameter={0}", 145 | _observableDbOperation.Parameters[0].Value); 146 | _observableDbOperation.ExecuteReaderWithUpdates(ProcessRecord); 147 | _traceSource.TraceInformation("OracleReceiver.GetLastPayloadId returned"); 148 | } 149 | 150 | private void ProcessRecord(IDataRecord record, IDbOperation dbOperation) 151 | { 152 | long id = record.GetInt64(0); 153 | ScaleoutMessage message = OraclePayload.FromBytes(record); 154 | 155 | _traceSource.TraceVerbose("OracleReceiver last payload ID={0}, new payload ID={1}", _lastPayloadId, id); 156 | 157 | if (id > _lastPayloadId + 1) 158 | { 159 | _traceSource.TraceError("Missed message(s) from Oracle. Expected payload ID {0} but got {1}.", _lastPayloadId + 1, id); 160 | } 161 | else if (id <= _lastPayloadId) 162 | { 163 | _traceSource.TraceInformation("Duplicate message(s) or payload ID reset from Oracle. Last payload ID {0}, this payload ID {1}", _lastPayloadId, id); 164 | } 165 | 166 | _lastPayloadId = id; 167 | dbOperation.Parameters[0].Value = _lastPayloadId; 168 | 169 | _traceSource.TraceVerbose("Updated receive reader initial payload ID parameter={0}", _observableDbOperation.Parameters[0].Value); 170 | _traceSource.TraceVerbose("Payload {0} containing {1} message(s) received", id, message.Messages.Count); 171 | 172 | if (Received != null) 173 | { 174 | Received((ulong) id, message); 175 | } 176 | } 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/OracleScaleoutConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNet.SignalR.Messaging; 3 | 4 | namespace Sdl.SignalR.OracleMessageBus 5 | { 6 | /// 7 | /// Settings for the Oracle scale-out message bus implementation. 8 | /// 9 | public class OracleScaleoutConfiguration : ScaleoutConfiguration 10 | { 11 | public OracleScaleoutConfiguration(string connectionString) 12 | : this(connectionString, false) 13 | { 14 | } 15 | 16 | /// 17 | /// Initializes a new instance of the class. 18 | /// 19 | public OracleScaleoutConfiguration(string connectionString, bool useOracleDependency = false, int? oracleDependencyPort = null) 20 | { 21 | if (string.IsNullOrEmpty(connectionString)) 22 | { 23 | throw new ArgumentNullException("connectionString"); 24 | } 25 | 26 | ConnectionString = connectionString; 27 | UseOracleDependency = useOracleDependency; 28 | OracleDependencyPort = oracleDependencyPort; 29 | } 30 | 31 | /// 32 | /// The Oracle connection string to use. 33 | /// 34 | public string ConnectionString 35 | { 36 | get; 37 | private set; 38 | } 39 | 40 | /// 41 | /// Specifies to use Oracle Query Notification mechanism after the certain amount 42 | /// of attempts to get new messages from database has been made and do not use polling. 43 | /// 44 | public bool UseOracleDependency 45 | { 46 | get; 47 | private set; 48 | } 49 | 50 | /// 51 | /// Indicates the port number that the notification listener listens on, for database notifications. 52 | /// 53 | public int? OracleDependencyPort 54 | { 55 | get; 56 | private set; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/OracleSender.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Data; 3 | using System.Diagnostics; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNet.SignalR.Messaging; 6 | using Oracle.ManagedDataAccess.Client; 7 | 8 | namespace Sdl.SignalR.OracleMessageBus 9 | { 10 | internal class OracleSender : IOracleSender 11 | { 12 | private readonly string _connectionString; 13 | private readonly string _insertSql; 14 | private readonly TraceSource _traceSource; 15 | private readonly IDbProviderFactory _dbProviderFactory; 16 | private readonly IDbOperationFactory _dbOperationFactory; 17 | 18 | public OracleSender(string connectionString, 19 | TraceSource traceSource, 20 | IDbProviderFactory dbProviderFactory, 21 | IDbOperationFactory dbOperationFactory) 22 | { 23 | _connectionString = connectionString; 24 | _insertSql = "SIGNALR.PAYLOAD_INSERT"; 25 | _traceSource = traceSource; 26 | _dbProviderFactory = dbProviderFactory; 27 | _dbOperationFactory = dbOperationFactory; 28 | } 29 | 30 | public Task Send(IList messages) 31 | { 32 | if (messages == null || messages.Count == 0) 33 | { 34 | return MakeEmptyTask(); 35 | } 36 | 37 | IDataParameter parameter = _dbProviderFactory.CreateParameter(); 38 | parameter.ParameterName = "iPayload"; 39 | parameter.Value = OraclePayload.ToBytes(messages); 40 | 41 | OracleParameter oracleParameter = parameter as OracleParameter; 42 | if (oracleParameter != null) 43 | { 44 | oracleParameter.OracleDbType = OracleDbType.Blob; 45 | } 46 | 47 | var operation = _dbOperationFactory.CreateDbOperation(_connectionString, _insertSql, _traceSource, _dbProviderFactory, parameter); 48 | return operation.ExecuteNonQueryAsync(); 49 | } 50 | 51 | private Task MakeEmptyTask() 52 | { 53 | var tcs = new TaskCompletionSource(); 54 | tcs.SetResult(null); 55 | return tcs.Task; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/OracleStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Globalization; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using Microsoft.AspNet.SignalR.Messaging; 9 | 10 | namespace Sdl.SignalR.OracleMessageBus 11 | { 12 | internal class OracleStream : IDisposable 13 | { 14 | private readonly int _streamIndex; 15 | private readonly TraceSource _trace; 16 | private readonly IOracleSender _sender; 17 | private readonly IOracleReceiver _receiver; 18 | private readonly string _tracePrefix; 19 | private readonly BlockingCollection _messageCollection; 20 | private readonly CancellationTokenSource _cts; 21 | 22 | public OracleStream(int streamIndex, 23 | string connectionString, 24 | bool useOracleDependency, 25 | TraceSource traceSource, 26 | IDbProviderFactory dbProviderFactory, 27 | IDbOperationFactory dbOperationFactory, 28 | IObservableDbOperationFactory observableDbOperationFactory) 29 | { 30 | _streamIndex = streamIndex; 31 | _trace = traceSource; 32 | _tracePrefix = string.Format(CultureInfo.InvariantCulture, "Stream {0} : ", _streamIndex); 33 | _cts = new CancellationTokenSource(); 34 | 35 | _messageCollection = new BlockingCollection(); 36 | _sender = new OracleSender(connectionString, _trace, dbProviderFactory, dbOperationFactory); 37 | _receiver = new OracleReceiver(connectionString, useOracleDependency, _trace, dbProviderFactory, dbOperationFactory, observableDbOperationFactory); 38 | _receiver.Queried += () => 39 | { 40 | if (Queried != null) 41 | { 42 | Queried(); 43 | } 44 | }; 45 | _receiver.Faulted += ex => 46 | { 47 | if (Faulted != null) 48 | { 49 | Faulted(ex); 50 | } 51 | }; 52 | _receiver.Received += OnReceived; 53 | StartReceiving(_cts.Token); 54 | } 55 | 56 | public event Action Queried; 57 | public event Action Faulted; 58 | public event Action Received; 59 | 60 | public void GetLastPayloadId() 61 | { 62 | _receiver.GetLastPayloadId(); 63 | } 64 | 65 | public void StartReceivingUpdatesFromDb() 66 | { 67 | _receiver.StartReceivingUpdatesFromDb(); 68 | } 69 | 70 | public Task Send(IList messages) 71 | { 72 | _trace.TraceVerbose("{0}Saving payload with {1} messages(s) to Oracle", _tracePrefix, messages.Count); 73 | return _sender.Send(messages); 74 | } 75 | 76 | public void Dispose() 77 | { 78 | if (_cts != null) 79 | { 80 | _cts.Cancel(); 81 | _cts.Dispose(); 82 | } 83 | 84 | _trace.TraceInformation("{0}Disposing stream {1}", _tracePrefix, _streamIndex); 85 | _receiver.Dispose(); 86 | } 87 | 88 | private void OnReceived(ulong id, ScaleoutMessage messages) 89 | { 90 | _messageCollection.Add(new MessageWrapper((long)id, messages)); 91 | } 92 | 93 | private void StartReceiving(CancellationToken token) 94 | { 95 | Task.Factory.StartNew(() => 96 | { 97 | while (true) 98 | { 99 | if (token.IsCancellationRequested) 100 | { 101 | return; 102 | } 103 | 104 | try 105 | { 106 | MessageWrapper message = _messageCollection.Take(token); 107 | if (Received != null) 108 | { 109 | Received((ulong)message.Id, message.Message); 110 | } 111 | } 112 | catch (TaskCanceledException) { } // do not need to catch, because it is an expected error during shutdown. 113 | } 114 | }, TaskCreationOptions.LongRunning); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | [assembly: AssemblyTrademark("")] 5 | [assembly: AssemblyCulture("")] 6 | 7 | // Setting ComVisible to false makes the types in this assembly not visible 8 | // to COM components. If you need to access a type in this assembly from 9 | // COM, set the ComVisible attribute to true on that type. 10 | [assembly: ComVisible(false)] 11 | 12 | // The following GUID is for the ID of the typelib if this project is exposed to COM 13 | [assembly: Guid("286e7ded-376d-45a2-a1a6-e2dfc1431360")] 14 | [assembly: InternalsVisibleTo("Sdl.SignalR.OracleMessageBus.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100a793292849ba1a1e85219d8f741ba2daea53e2bd5da69b6209082c5f001685ba9053260ffef5258519b28a10bedf24341b594dc9092c699c871bc3e0fd86777f2dd0dd7e918432f3a7d6c89b1b51fdfea5026fbc8d47db378bfe533669cc7a9b6f76adf8585cea48abf45f0b96a2ed0ab3d94074017a8c505aa787bf1d7751d0")] 15 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/SDL.SignalR.OracleMessageBus.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net48 4 | Debug;Release 5 | Sdl.SignalR.OracleMessageBus 6 | Sdl.SignalR.OracleMessageBus 7 | true 8 | false 9 | ..\..\shared\Sdl.SignalR.OracleMessageBus.snk 10 | Sdl.SignalR.OracleMessageBus 11 | Sdl.SignalR.OracleMessageBus 12 | Oracle messaging backplane for scaling out of ASP.NET SignalR applications in a web-farm. 13 | http://dr0muzwhcp26z.cloudfront.net/static/corporate/SDL-logo-2014.png 14 | https://community.sdl.com/developers/tridion_developer/w/wiki/864.sdl-web-developer-software-and-distribution-agreement 15 | https://github.com/sdl/SignalR-OracleMessageBus 16 | Initial release of Oracle backplane which supports only single stream. 17 | SDL,SignalR,Backplane,MessageBus,Oracle 18 | true 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | TRACE;DEBUG;SERVER 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | Debug 39 | AnyCPU 40 | {286E7DED-376D-45A2-A1A6-E2DFC1431360} 41 | Library 42 | Properties 43 | 512 44 | 45 | true 46 | false 47 | ..\..\shared\Sdl.SignalR.OracleMessageBus.snk 48 | true 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Always 62 | 63 | 64 | -------------------------------------------------------------------------------- /src/SDL.SignalR.OracleMessageBus/install.sql: -------------------------------------------------------------------------------- 1 | -- Connection user must have the following system priviliges: 2 | -- 1. Create any tables 3 | -- 2. Create any sequences 4 | -- 3. Create any procedures 5 | -- 4. Optional. In case it is expected that data notification will be used (in lieu of polling), 6 | -- connection user must be granted with change notification privilege: GRANT CHANGE NOTIFICATION TO &DBOWNER 7 | DECLARE 8 | lCount NUMBER; 9 | BEGIN 10 | SELECT COUNT(1) INTO lCount FROM USER_TABLES WHERE TABLE_NAME = 'MESSAGES'; 11 | 12 | IF lCount = 0 THEN 13 | EXECUTE IMMEDIATE 14 | 'CREATE TABLE MESSAGES( 15 | PayloadId NUMBER(11,0) NOT NULL, 16 | Payload BLOB NOT NULL, 17 | InsertedOn DATE NOT NULL, 18 | CONSTRAINT PK_PAYLOAD_ID PRIMARY KEY(PayloadId) 19 | ) TABLESPACE &TBLSPC_TDS_TABLES'; 20 | END IF; 21 | 22 | SELECT COUNT(1) INTO lCount FROM USER_SEQUENCES WHERE SEQUENCE_NAME = 'SEQ_MESSAGES'; 23 | 24 | IF lCount = 0 THEN 25 | EXECUTE IMMEDIATE 'CREATE SEQUENCE SEQ_MESSAGES MAXVALUE 99999999999 CYCLE START WITH 1 INCREMENT BY 1 NOCACHE'; 26 | END IF; 27 | 28 | SELECT COUNT(1) INTO lCount FROM user_objects WHERE OBJECT_TYPE = 'PACKAGE' AND OBJECT_NAME = 'SIGNALR'; 29 | IF lCount = 0 THEN 30 | EXECUTE IMMEDIATE 31 | 'CREATE PACKAGE SIGNALR AS 32 | PROCEDURE PAYLOAD_INSERT( 33 | iPayload IN BLOB 34 | ); 35 | PROCEDURE PAYLOADID_GET( 36 | oPayloadId OUT MESSAGES.PayloadId%TYPE 37 | ); 38 | PROCEDURE PAYLOAD_READ( 39 | iPayloadId IN MESSAGES.PayloadId%TYPE, 40 | oRefCur OUT SYS_REFCURSOR 41 | ); 42 | END SIGNALR;'; 43 | 44 | EXECUTE IMMEDIATE 45 | 'CREATE PACKAGE BODY SIGNALR AS 46 | 47 | PROCEDURE PAYLOAD_INSERT( 48 | iPayload IN BLOB 49 | ) AS 50 | lNewPayloadId MESSAGES.PayloadId%TYPE; 51 | MaxTableSize CONSTANT NUMBER := 10000; 52 | BlockSize CONSTANT NUMBER := 2500; 53 | lRowCount NUMBER; 54 | lStartPayloadId MESSAGES.PayloadId%TYPE; 55 | lEndPayloadId MESSAGES.PayloadId%TYPE; 56 | lOverMaxBy NUMBER; 57 | BEGIN 58 | SELECT SEQ_MESSAGES.NEXTVAL INTO lNewPayloadId FROM DUAL; 59 | INSERT INTO MESSAGES(PayloadId, Payload,InsertedOn) VALUES(lNewPayloadId, iPayload, SYS_EXTRACT_UTC(SYSTIMESTAMP)); 60 | 61 | COMMIT; 62 | -- Garbage collection 63 | SET TRANSACTION ISOLATION LEVEL READ COMMITTED; 64 | IF lNewPayloadId MOD BlockSize = 0 THEN 65 | SELECT COUNT(PayloadId), MIN(PayloadId) INTO lRowCount, lStartPayloadId FROM MESSAGES; 66 | 67 | -- Check if we are over the max table size 68 | IF lRowCount >= MaxTableSize THEN 69 | 70 | -- We want to delete enough rows to bring the table back to max size - block size 71 | lOverMaxBy := lRowCount - MaxTableSize; 72 | lEndPayloadId := lStartPayloadId + BlockSize + lOverMaxBy; 73 | 74 | -- Delete oldest block of messages 75 | DELETE FROM MESSAGES WHERE PayloadId BETWEEN lStartPayloadId AND lEndPayloadId; 76 | END IF; 77 | COMMIT; 78 | END IF; 79 | 80 | END PAYLOAD_INSERT; 81 | 82 | PROCEDURE PAYLOADID_GET( 83 | oPayloadId OUT MESSAGES.PayloadId%TYPE 84 | ) AS 85 | lPayloadId MESSAGES.PayloadId%TYPE; 86 | BEGIN 87 | SELECT LAST_NUMBER - 1 INTO oPayloadId FROM USER_SEQUENCES WHERE SEQUENCE_NAME = ''SEQ_MESSAGES''; 88 | END PAYLOADID_GET; 89 | 90 | PROCEDURE PAYLOAD_READ( 91 | iPayloadId IN MESSAGES.PayloadId%TYPE, 92 | oRefCur OUT SYS_REFCURSOR 93 | ) AS 94 | BEGIN 95 | OPEN oRefCur FOR 96 | SELECT PayloadId, Payload, InsertedOn FROM MESSAGES 97 | WHERE PayloadId > iPayloadId 98 | ORDER BY PayloadId ASC; 99 | END PAYLOAD_READ; 100 | END SIGNALR;'; 101 | END IF; 102 | 103 | END; -------------------------------------------------------------------------------- /test.runsettings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 7 | x64 8 | 9 | --------------------------------------------------------------------------------