how to run testcontainer with dynamic port for spring data elasticsearch - spring-data-elasticsearch

My test case uses #SpringBootTest annotations to bring up the context and has Autowired some repository. Testcontainer is started in #BeforeAll() method. The problem is RestClientConfig is being initialized/injected before #BeforeAll() in test case. When testcontainer starts, it exports some dynamic port.
I have to set some fixed port in testcontainer 34343 and use the same port in properties file for RestClientConfig.
container = new ElasticsearchContainer(ELASTICSEARCH_IMAGE)
.withEnv("discovery.type", "single-node")
.withExposedPorts(9200)
.withCreateContainerCmdModifier(cmd -> cmd.withHostConfig(
new HostConfig().withPortBindings(new PortBinding(Ports.Binding.bindPort(34343), new ExposedPort(9200)))));
Is there a way to start container and get its dynamic port then use it to initialize RestClientConfig?
I didn't use annoation #Testcontainers though. Is it needed?

Newer versions of Spring provide #DynamicPropertySource for exactly this use case:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/DynamicPropertySource.html
Your code should look roughly like this:
#SpringJUnitConfig(...)
#Testcontainers
class ExampleIntegrationTests {
#Container
static ElasticsearchContainer elastic= new ElasticsearchContainer(ELASTICSEARCH_IMAGE)
.withEnv("discovery.type", "single-node");
// ...
#DynamicPropertySource
static void elasticProperties(DynamicPropertyRegistry registry) {
registry.add("spring.elasticsearch.uris", elastic::getHttpHostAddress);
}
}

You can use context configuration initialiser to set properties during runtime, which you can later use in your RestClientConfig.
Let me show you on the example of Postgresql container setup:
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT, classes = Application.class)
#ContextConfiguration(initializers = AbstractTestcontainersTest.DockerPostgreDataSourceInitializer.class)
public abstract class AbstractTestcontainersTest {
protected static final String DB_CONTAINER_NAME = "postgres-auth-test";
protected static PostgreSQLContainer<?> postgreDBContainer =
new PostgreSQLContainer<>(DockerImageName.parse("public.ecr.aws/docker/library/postgres:12.10-alpine")
.asCompatibleSubstituteFor("postgres"))
.withUsername("postgres")
.withPassword("change_me")
.withInitScript("db.sql")
.withCreateContainerCmdModifier(cmd -> cmd.withName(DB_CONTAINER_NAME))
.withDatabaseName("zpot_main");
#BeforeAll
public static void beforeAll() throws ShellExecutionException {
postgreDBContainer.start();
}
#AfterAll
public static void afterAll() {
postgreDBContainer.stop();
}
public static class DockerPostgreDataSourceInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertySourceUtils.addInlinedPropertiesToEnvironment(
applicationContext,
"spring.datasource.url=" + postgreDBContainer.getJdbcUrl(),
"spring.datasource.username=" + postgreDBContainer.getUsername(),
"spring.datasource.password=" + postgreDBContainer.getPassword()
);
}
}
}
All the configuration is done in DockerPostgreDataSourceInitializer, where I set all the properties I need. You also need to annotate your test class with #ContextConfiguration annotaion. You can do something similar with your ElasticSearchContainer. As I just checked the ElasticSearchContainer has a method getHttpHostAddress() which returns host+dynamic_port combination for your container. You can get that host-port pair and set in in properties to be used later in your client configuration. If you need just port you can call container.getMappedPort(9200) and again set that port in properties.
Regarding #Testcontainers annotation, you need it if you want testcontainers to manage your container lifecycle. In that case you also need to annotate container with #Container annotation. Your container will be started either once before all test methods in a class if your container is a static field or before each test method if it's a regular field. You can read more about that here: https://www.testcontainers.org/test_framework_integration/junit_5/#extension.
Or you can start your container manually either in #BeforeAll or #BeforeEach annotated setup methods. In other words no, you don't have to use #Testcontainers annotation.

Related

How to dynamically instantiate a proxy class?

I have used Castle.DynamicProxy to create an interceptor that implements IInterceptor. This interceptor does some work related with logging.
I have successfully injected this into multiple classes using the default Microsoft Dependency Injection and I also was able to do so using Autofac.
Microsoft Dependency Injection:
public static void AddLoggedScoped<TService, TImplementation>(this IServiceCollection pServices)
where TService : class
where TImplementation : class, TService
{
pServices.TryAddScoped<IProxyGenerator, ProxyGenerator>();
pServices.AddScoped<TImplementation>();
pServices.TryAddTransient<LoggingInterceptor>();
pServices.AddScoped(provider =>
{
var proxyGenerator = provider.GetRequiredService<IProxyGenerator>();
var service = provider.GetRequiredService<TImplementation>();
var interceptor = provider.GetRequiredService<LoggingInterceptor>();
return proxyGenerator.CreateInterfaceProxyWithTarget<TService>(service, interceptor);
});
}
Autofac Dependency Injection:
builder.RegisterType<DITest>( ).As<IDITest>()
.EnableInterfaceInterceptors()
.InterceptedBy(typeof(LoggingInterceptorAdapter<LoggingInterceptor>));
Despite this I would also like to inject it in classes dynamically instantiated (for instances, classes that are instantiated accordingly to a value - factory pattern). My factory instantiates different concretizations of an interface depending on a value provided by parameter. Something along these lines:
public IApple Create(string color)
{
IApple fruit;
switch (color)
{
case "green":
fruit = new GreenApple();
break;
case "red":
fruit = new RedApple();
}
return fruit;
}
The interface IFruit looks like these:
public interface IFruit
{
void Cut();
void Eat();
string GetNutrionalInfo();
}
What I am trying to achieve is a way to inject/add an interceptor to the concretization of RedApple() that would allow me to know when methods such as redApple.Cut() are called.
What is the best way to do so? I was under the impression that Autofac would allow this, but I have not been successful.
What you will need to do is update your factory to use service location instead of directly constructing things. Basically, instead of using new, you'll need to use Autofac or Microsoft DI (assuming Autofac is configured as the backing container) to resolve the thing.
First, whenever you need your factory, make sure you are injecting it and not just calling new. Everything involved in this chain needs to go through Autofac.
public class UsesTheFactory
{
private IFactory _factory;
public UsesTheFactory(IFactory factory)
{
this._factory = factory;
}
}
You will, of course, need to register the thing that uses the factory.
builder.RegisterType<UsesTheFactory>();
Next, inject the lifetime scope into the factory and use it for service location. This is how you get the proxy and all that into the created objects.
public class MyFactory : IFactory
{
private readonly ILifetimeScope _scope;
public MyFactory(ILifetimeScope scope)
{
this._scope = scope;
}
public IApple Create(string color)
{
IApple fruit;
switch (color)
{
case "green":
fruit = this._scope.Resolve<GreenApple>();
break;
case "red":
fruit = this._scope.Resolve<RedApple>();
}
return fruit;
}
}
You'll need to register the factory and the things that the factory needs to resolve.
builder.RegisterType<MyFactory>().As<IFactory>();
builder.RegisterType<RedApple>();
builder.RegisterType<GreenApple>();
Finally, whenever you need something that uses the factory, that thing needs to be resolved. In this example, you can't really ever just new UsesTheFactory() - you have to resolve it (or have it injected into something else).
var builder = new ContainerBuilder();
builder.RegisterType<UsesTheFactory>();
builder.RegisterType<MyFactory>().As<IFactory>();
builder.RegisterType<RedApple>();
builder.RegisterType<GreenApple>();
var container = builder.Build();
using var scope = container.BeginLifetimeScope();
var user = scope.Resolve<UsesTheFactory>();
user.DoSomethingThatCallsTheFactory();
The key principle is that if you need that proxy injected anywhere in the pipeline, you can't use new. Full stop. If you need that thing, it needs to flow through Autofac somehow.

Deploying a transaction event listener in a Neo4jDesktop installation

I have created a project that contains an ExtensionFactory subclass annotated as #ServiceProvider that returns a LifecycleAdapter subclass which registers a transaction event listener in its start() method, as shown in this example. The code is below:
#ServiceProvider
public class EventListenerExtensionFactory extends ExtensionFactory<EventListenerExtensionFactory.Dependencies> {
private final List<TransactionEventListener<?>> listeners;
public EventListenerExtensionFactory() {
this(List.of(new MyListener()));
}
public EventListenerExtensionFactory(List<TransactionEventListener<?>> listeners) {
super(ExtensionType.DATABASE, "EVENT_LISTENER_EXT_FACTORY");
this.listeners = listeners;
}
#Override
public Lifecycle newInstance(ExtensionContext context, Dependencies dependencies) {
return new EventListenerLifecycleAdapter(dependencies, listeners);
}
#RequiredArgsConstructor
private static class EventListenerLifecycleAdapter extends LifecycleAdapter {
private final Dependencies dependencies;
private final List<TransactionEventListener<?>> listeners;
#Override
public void start() {
DatabaseManagementService managementService = dependencies.databaseManagementService();
listeners.forEach(listener -> managementService.registerTransactionEventListener(
DEFAULT_DATABASE_NAME, listener));
dependencies.log()
.getUserLog(EventListenerExtensionFactory.class)
.info("Registering transaction event listener for database " + DEFAULT_DATABASE_NAME);
}
}
interface Dependencies {
DatabaseManagementService databaseManagementService();
LogService log();
}
}
It works fine in an integration test:
public AbstractDatabaseTest(TransactionEventListener<?>... listeners) {
URI uri = Neo4jBuilders.newInProcessBuilder()
.withExtensionFactories(List.of(new EventListenerExtensionFactory(List.of(listeners))))
.withDisabledServer()
.build()
.boltURI();
driver = GraphDatabase.driver(uri);
session = driver.session();
}
Then I copy the jar file in the plugins directory of my desktop database:
$ cp build/libs/<myproject>.jar /mnt/c/Users/albert.gevorgyan/.Neo4jDesktop/relate-data/dbmss/dbms-7fe3cbdb-11b2-4ca2-81eb-474edbbb3dda/plugins/
I restart the database and even the whole desktop Neo4j program but it doesn't seem to identify the plugin or to initialize the factory: no log messages are found in neo4j.log after the start event, and the transaction events that should be captured by my listener are ignored. Interestingly, a custom function that I have defined in the same jar file actually works - I can call it in the browser. So something must be missing in the extension factory as it doesn't get instantiated.
Is it possible at all to deploy an ExtensionFactory in a Desktop installation and if yes, what am I doing wrong?
It works after I added a provider configuration file to META-INF/services, as explained in https://www.baeldung.com/java-spi. Neo4j finds it then.

Context Injection and BeforeFeature hook

I am trying to do some intial setup which sets up the environment for the scenarios under a particular feature. I clubbed the setup data in a separate class. I added a specflow hook file and modified it to serve as before feature hook. Then I tried using Context Injection. In the I created a private variable of the setup class and a constructor (taking an object of the setup class) for the BeforeScenario hook file.
The issue I am facing is that the BeforeFeature method has to static as per specflow. And if I make my private setup class static, then the constructor is not getting called.
Is what I am doing right? Or Is it even possible what I am trying to do?
[Binding]
public class BeforeFeature
{
private static SetUp setUp;
public BeforeFeature(SetUp setUpObject)
{
setUp = setUpObject;
}
[BeforeFeature]
public static void RunBeforeFeature()
{
//Some processing.
setUp.baseDir = "some data";
setUp.status = "some data"
}
}
You can tell SpecFlows context injection framework that you have an object it should use when a Step class asks for an instance in its constructor. This can be done like shown in the example:
[Binding]
public class BeforeFeature
{
private readonly IObjectContainer objectContainer;
private static SetUp setUp;
public BeforeFeature(IObjectContainer container)
{
this.objectContainer = objectContainer;
}
[BeforeFeature]
public static void RunBeforeFeature()
{
//Some processing.
setUp.baseDir = "some data";
setUp.status = "some data"
}
[BeforeScenario]
public void RunBeforeScenario()
{
objectContainer.RegisterInstanceAs<SetUp>(setUp);
}
}
You do the setup before the Scenario not the feature but if you only create the SetUp once and set its values in the [BeforeFeature] then you should get the same instance in each scenario so any modifications made there should stick (assuming this is what you want, otherwise just create a new SetUp in the BeforeScenario method)
As long as your Step classes now ask for a SetUp instance in the constructor they should get the one you placed in the container.

Instance method with Guice

I would like to have a static instance method with Guice for one of the components (non-managed bean should be able to access this class). I created something like this:
public class LookupService {
#Inject
private static Provider<Injector> injector = null;
private final ILookup<IWS> lookup;
#Inject
public LookupService(ILookup<IWS> lookup) {
this.lookup = lookup;
}
public static LookupService instance() {
return injector.get().getInstance(LookupService.class);
}
public <T extends IWS> T lookup(Class<T> localInterface) {
return lookup.lookup(localInterface);
}
}
What do you think about this design ? Any other ideas on this ? (accessing managed beans from non-managed objects)
Basically, the pattern you're looking for is called "requesting static injection" and there's a Binder method dedicated to it. Once you have that down, your code looks a lot like this example from the Guice docs.
public class MainModule extends AbstractModule {
#Override public void configure() {
requestStaticInjection(LookupService.class);
}
}
public class LookupService {
/** This will be set as soon as the injector is created. */
#Inject
static Provider<LookupService> provider = null;
private final ILookup<IWS> lookup;
#Inject
public LookupService(ILookup<IWS> lookup) {
this.lookup = lookup;
}
public static LookupService instance() {
return provider.get();
}
public <T extends IWS> T lookup(Class<T> localInterface) {
return lookup.lookup(localInterface);
}
}
A few notes:
While you can still set your field to be private, remember that this means you cannot set it in tests (or in future non-Guice usage) without Guice's private-field-access magic. When using injected fields, we often make them package-private and then put the tests in the same package.
Static injection is generally seen as something to endorse only when migrating to Guice, or when you use other code you can't change. When possible, try to avoid global state--even if this means making FooBean data-only and creating an injected FooBeanService.
Even though you can inject an Injector wherever you'd like, you might find it easier to test if you simply inject a Provider<LookupService> instead. Only inject an Injector if you don't know what type you're going to need until runtime--for example, if you implement LookupService.lookup(...) using an Injector by passing the class literal to the injector to get an instance.
In fact, it's hard to say from here, but ILookup seems to act a lot like the Service Locator pattern, which solves the exact type of problem that Guice solves with dependency injection! If that's the case, you might as well rewrite ILookup to use Guice: Just remove calls to LookupService.instance().lookup(Foo.class) and instead create a matching pair of #Inject static Provider<Foo> fooProvider and requestStaticInjection(FooUser.class).
Hope that helps!

How to inject AutoMapper IMappingEngine with StructureMap

Most of the examples I've found for Automapper use the static Mapper object for managing type mappings. For my project, I need to inject an IMapperEngine as part of object construction using StructureMap so that we can mock the mapper in unit tests so we can't use the static mapper. I also need to support configuring AutoMapper Profiles.
My question is how can I configure the StructureMap registry so that it can supply an instance of IMappingEngine when an instance of MyService is constructed.
Here is the Service constructor signature:
public MyService(IMappingEngine mapper, IMyRepository myRepository, ILogger logger)
And here is the StructureMap Registry
public class MyRegistry : StructureMap.Configuration.DSL.Registry
{
public MyRegistry()
{
For<IMyRepository>().Use<MyRepository>();
For<ILogger>().Use<Logger>();
//what to do for IMappingEngine?
}
}
And the profile I want to load
public class MyAutoMapperProfile : AutoMapper.Profile
{
protected override void Configure()
{
this.CreateMap<MyModel, MyDTO>();
}
}
The Mapper class has a static property Mapper.Engine. Use this to register the engine with the container:
For<IMappingEngine>().Use(() => Mapper.Engine);
If you need to load your profiles before injecting the engine I would insert that configuration code alongside the above snippet.
Update
Your custom registry would look like this
class MyRegistry : Registry
{
public MyRegistry()
{
For<IMyRepository>().Use<MyRepository>();
For<ILogger>().Use<Logger>();
Mapper.AddProfile(new AutoMapperProfile());
For<IMappingEngine>().Use(() => Mapper.Engine);
}
}
This code runs once in your bootstrapper and any dependency of type IMappingEngine will afterwards be served with the value of the static property Mapper.Engine which is configured using your custom AutoMapperProfile.
The static API will be removed in version 5.0. Use a
MapperConfiguration instance and store statically as needed. Use
CreateMapper to create a mapper instance.
in new version (4.2.0 >=) we should hold and pass IMapper through DI.
a simple Configure Service should be like this (ASP.NET Core)
services.AddSingleton<IMapper>(_ => new MapperConfiguration(cfg =>
{
cfg.CreateMap<Foo,Bar>();
})
.CreateMapper());
and our service layer (with the help of constructor injection) :
public class CrudService<TDocument> : ICrudService<TDocument>
{
private readonly IMapper _internalMapper;
private readonly IRepository<TDocument> _repository;
public CrudService(IRepository<TDocument> repository, IMapper mapper)
{
_internalMapper = mapper;
_repository = repository;
}
public virtual ServiceResult<string> Create<TModel>(TModel foo)
{
var bar = _internalMapper.Map<TDocument>(foo);
try
{
_repository.Create(bar);
}
catch (Exception ex)
{
return ServiceResult<string>.Exception(ex);
}
return ServiceResult<string>.Okay(entity.Id);
}
}
consider TDocument as Bar, and TModel as Foo
update :
AutoMapper 4.2.1 released – Static is back
After a bit of feedback and soul searching and honestly tired of
dealing with questions, some of the static API is restored in this
release. You can now (and in the future) use Mapper.Initialize and
Mapper.Map
I wrote a blog post that shows my AutoMapper with StructureMap setup. I have created specific registries for AutoMapper 3.1.0 (also works for 3.1.1) and 3.0.0 and 2.2.1.
http://www.martijnburgers.net/post/2013/12/20/My-AutoMapper-setup-for-StructureMap.aspx
Here's what I ended up with as I couldn't figure out how to set the configuration on Mapper.Engine and have it passed into For().Use.
public MyRegistry()
{
For<IMyRepository>().Use<MyRepository>();
For<ILogger>().Use<Logger>();
//type mapping
For<ConfigurationStore>()
.Singleton()
.Use(ctx =>
{
ITypeMapFactory factory = ctx.GetInstance<ITypeMapFactory>();
ConfigurationStore store
= new ConfigurationStore(factory, MapperRegistry.AllMappers());
IConfiguration cfg = store;
cfg.AddProfile<MyAutoMapperProfile>();
store.AssertConfigurationIsValid();
return store;
});
For<IConfigurationProvider>().Use(ctx => ctx.GetInstance<ConfigurationStore>());
For<IConfiguration>().Use(ctx => ctx.GetInstance<ConfigurationStore>());
For<IMappingEngine>().Singleton().Use<MappingEngine>();
For<ITypeMapFactory>().Use<TypeMapFactory>();
}

Resources