Merge pull request #188 from cezarypiatekGC/feature/add_credential_connection_factory

Add UserCredentialsConnectionFactoryAdapter
This commit is contained in:
Marko Lahma
2019-09-07 10:43:58 +03:00
committed by GitHub
3 changed files with 377 additions and 0 deletions

View File

@@ -0,0 +1,161 @@
#region License
/*
* Copyright 2002-2010 the original author or authors.
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.Threading;
using Apache.NMS;
namespace Spring.Messaging.Nms.Connections
{
/// <summary>
/// An adapter for a target JMS {@link javax.jms.ConnectionFactory}, applying the
/// given user credentials to every standard <code>CreateConnection()</code> call,
/// that is, implicitly invoking <code>CreateConnection(username, password)</code>
/// on the target.All other methods simply delegate to the corresponding methods
/// of the target ConnectionFactory.
/// </summary>
/// <remarks>
/// Can be used to proxy a target NMS ConnectionFactory that does not have user
/// credentials configured. Client code can work with the ConnectionFactory without
/// passing in username and password on every <code>CreateConnection()</code> call.
/// If the "Username" is empty, this proxy will simply delegate to the standard
/// <code>CreateConnection()</code> method of the target ConnectionFactory.
/// This can be used to keep a UserCredentialsConnectionFactoryAdapter
/// definition just for the<i> option</i> of implicitly passing in user credentials
/// if the particular target ConnectionFactory requires it.
/// </remarks>
public class UserCredentialsConnectionFactoryAdapter:IConnectionFactory
{
private readonly IConnectionFactory _wrappedConnectionFactory;
private readonly ThreadLocal<NmsUserCredentials> threadLocalCredentials = new ThreadLocal<NmsUserCredentials>();
public UserCredentialsConnectionFactoryAdapter(IConnectionFactory wrappedConnectionFactory)
{
this._wrappedConnectionFactory = wrappedConnectionFactory;
}
/// <summary>
/// Set user credentials for this proxy and the current thread.
/// The given username and password will be applied to all subsequent
/// <code>CreateConnection()</code> calls on this ConnectionFactory proxy.
/// This will override any statically specified user credentials,
/// that is, values of the "username" and "password" properties.
/// </summary>
public void SetCredentialsForCurrentThread(string userName, string password)
{
this.threadLocalCredentials.Value = new NmsUserCredentials(userName, password);
}
/// <summary>
/// Remove any user credentials for this proxy from the current thread.
/// Statically specified user credentials apply again afterwards.
/// </summary>
public void RemoveCredentialsFromCurrentThread()
{
this.threadLocalCredentials.Value = null;
}
private string _userName;
/// <summary>
/// Set the username that this adapter should use for retrieving Connections.
/// </summary>
public string UserName
{
get => _userName;
set => _userName = string.IsNullOrWhiteSpace(value) ? null : value;
}
private string _password;
/// <summary>
/// Set the password that this adapter should use for retrieving Connections.
/// </summary>
public string Password
{
get => _password;
set => _password = string.IsNullOrEmpty(value) ? null : value;
}
public IConnection CreateConnection()
{
var credentialsForCurrentThread = this.threadLocalCredentials.Value;
if (credentialsForCurrentThread != null)
{
return CreateConnectionForSpecificCredentials(credentialsForCurrentThread.UserName, credentialsForCurrentThread.Password);
}
return CreateConnectionForSpecificCredentials(UserName, Password);
}
private IConnection CreateConnectionForSpecificCredentials(string userName, string password)
{
if (string.IsNullOrWhiteSpace(userName) == false)
{
return CreateConnection(userName, password);
}
return _wrappedConnectionFactory.CreateConnection();
}
public IConnection CreateConnection(string userName, string password)
{
return _wrappedConnectionFactory.CreateConnection(userName, password);
}
public Uri BrokerUri
{
get => _wrappedConnectionFactory.BrokerUri;
set => _wrappedConnectionFactory.BrokerUri = value;
}
public IRedeliveryPolicy RedeliveryPolicy
{
get => _wrappedConnectionFactory.RedeliveryPolicy;
set => _wrappedConnectionFactory.RedeliveryPolicy = value;
}
public ConsumerTransformerDelegate ConsumerTransformer
{
get => _wrappedConnectionFactory.ConsumerTransformer;
set => _wrappedConnectionFactory.ConsumerTransformer = value;
}
public ProducerTransformerDelegate ProducerTransformer
{
get => _wrappedConnectionFactory.ProducerTransformer;
set => _wrappedConnectionFactory.ProducerTransformer = value;
}
private class NmsUserCredentials
{
public string UserName { get; }
public string Password { get; }
public NmsUserCredentials(string userName, string password)
{
UserName = userName;
Password = password;
}
}
}
}

View File

@@ -0,0 +1,52 @@
using System;
using System.Diagnostics;
using System.Threading;
namespace Spring.Messaging.Nms.Connections
{
public class MultithreadingTestHelper
{
[DebuggerStepThrough]
public static TestThreadHandler RunOnSeparateThread(Action action)
{
Exception exception = null;
var thread1 = new Thread(() =>
{
try
{
action();
}
catch (Exception e)
{
exception = e;
}
});
thread1.Start();
return new TestThreadHandler(() =>
{
thread1.Join();
if (exception != null)
{
throw exception;
}
});
}
public class TestThreadHandler
{
private readonly Action _waitAction;
public TestThreadHandler(Action waitAction)
{
_waitAction = waitAction;
}
[DebuggerStepThrough]
public void Wait()
{
_waitAction();
}
}
}
}

View File

@@ -0,0 +1,164 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Apache.NMS;
using FakeItEasy;
using NUnit.Framework;
namespace Spring.Messaging.Nms.Connections
{
[TestFixture]
public class UserCredentialsConnectionFactoryAdapterTests
{
[Test]
public void CreateConnectionWithoutCredentialsWhenTheyAreNotSet()
{
var underlyingConnectionFactory = A.Fake<IConnectionFactory>();
var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappenedOnceExactly();
A.CallTo(() => underlyingConnectionFactory.CreateConnection(A<string>._, A<string>._)).MustHaveHappened(0, Times.Exactly);
}
[Test]
public void CreateConnectionWithoutCredentialsWhenLoginIsNotSet()
{
var underlyingConnectionFactory = A.Fake<IConnectionFactory>();
var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
connectionFactory.Password = "Secret";
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappenedOnceExactly();
A.CallTo(() => underlyingConnectionFactory.CreateConnection(A<string>._, A<string>._)).MustHaveHappened(0, Times.Exactly);
}
[Test]
public void CreateConnectionWithCredentialsWhenTheyAreSet()
{
var underlyingConnectionFactory = A.Fake<IConnectionFactory>();
var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
connectionFactory.UserName = "SampleUser";
connectionFactory.Password = "Secret";
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappenedOnceExactly();
}
[Test]
public void SetConnectionCredentialsOnlyForCurrentThread()
{
var underlyingConnectionFactory = A.Fake<IConnectionFactory>();
var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
// Call CreateConnection on thread that also called SetCredentialsForCurrentThread
MultithreadingTestHelper.RunOnSeparateThread(() =>
{
connectionFactory.SetCredentialsForCurrentThread("SampleUser", "Password");
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Password")).MustHaveHappenedOnceExactly();
}).Wait();
// Call CreateConnection on thread that didn't callSetCredentialsForCurrentThread
MultithreadingTestHelper.RunOnSeparateThread(() =>
{
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappenedOnceExactly();
A.CallTo(() => underlyingConnectionFactory.CreateConnection(A<string>._, A<string>._)).MustHaveHappenedOnceExactly();
}).Wait();
// Call CreateConnection on the main thread
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(2, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection(A<string>._, A<string>._)).MustHaveHappenedOnceExactly();
}
[Test]
public void InheritCredentialsIfTheyAreNotSetForSpecificThread()
{
var underlyingConnectionFactory = A.Fake<IConnectionFactory>();
var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
connectionFactory.UserName = "SampleUser";
connectionFactory.Password = "Secret";
// Call CreateConnection on thread that also called SetCredentialsForCurrentThread
MultithreadingTestHelper.RunOnSeparateThread(() =>
{
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappenedOnceExactly();
connectionFactory.SetCredentialsForCurrentThread("ThreadSampleUser", "ThreadPassword");
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("ThreadSampleUser", "ThreadPassword")).MustHaveHappenedOnceExactly();
connectionFactory.RemoveCredentialsFromCurrentThread();
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappened(2, Times.Exactly);
}).Wait();
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappened(3, Times.Exactly);
}
[Test]
public void SetDifferentCredentialsOnDifferentThreads()
{
var underlyingConnectionFactory = A.Fake<IConnectionFactory>();
var connectionFactory = new UserCredentialsConnectionFactoryAdapter(underlyingConnectionFactory);
connectionFactory.UserName = "SampleUser";
connectionFactory.Password = "Secret";
var barrier = new Barrier(2);
// Call CreateConnection on thread that also called SetCredentialsForCurrentThread
var thread1 = MultithreadingTestHelper.RunOnSeparateThread(() =>
{
connectionFactory.SetCredentialsForCurrentThread("Thread1SampleUser", "Password");
barrier.SignalAndWait();
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("Thread1SampleUser", "Password")).MustHaveHappenedOnceExactly();
});
// Call CreateConnection on thread that didn't callSetCredentialsForCurrentThread
var thread2 = MultithreadingTestHelper.RunOnSeparateThread(() =>
{
connectionFactory.SetCredentialsForCurrentThread("Thread2SampleUser", "Password");
barrier.SignalAndWait();
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("Thread2SampleUser", "Password")).MustHaveHappenedOnceExactly();
});
connectionFactory.CreateConnection();
A.CallTo(() => underlyingConnectionFactory.CreateConnection()).MustHaveHappened(0, Times.Exactly);
A.CallTo(() => underlyingConnectionFactory.CreateConnection("SampleUser", "Secret")).MustHaveHappenedOnceExactly();
thread1.Wait();
thread2.Wait();
}
}
}