I'm trying to setup a plugin system for cucumber-jvm bindings with Guice as DI. The idea is to run a beforeRequest() method before each When step in Cucumber. Plugins are separate .jar found in the classpath.
Since Cucumber requires an explicit Guice module declaration in cucumber.properties, that will be used to create an Injector, I can not use standard Guice Multibindings and have a separate Module per Plugin.
An idea how it should look like:
class CucumberClazz {
Set<Plugin> plugins;
public doRequest() {
for(Plugin plugin: plugins) plugin.beforeRequest();
/* Rest of the code */
}
}
Is there a fancy option to discover all implementations of Plugin in the classpath with Guice?
Related
My gradle project contains 3 sub-projects with one source file each:
root-project\
sub-project-abstract\
...AbstractFoo.java
sub-project-commons\
...ConcreteFoo.java (extends AbstractFoo)
sub-project-main\
...Main.java (instantiates ConcreteFoo)
build.gradle of sub-project-commons:
dependencies {
implementation(project(:sub-project-abstract))
}
build.gradle of sub-project-main:
dependencies {
implementation(project(:sub-project-commons))
}
The Main-class in sub-project-main is aware of ConcreteFoo, however, compilation fails with cannot access AbstractFoo.
For some reason, I expected sub-project-commons to "export" ConcreteFoo and AbstractFoo, since it's a implementation-dependency. Or in other words, form the perspective of sub-project-main, AbstractFoo is a transitive dependency.
However, this doesn't seem to be the case.
I know that I could probably make it work by explicitly adding sub-project-abstract as a direct dependency to sub-project-main. However, that's something I want to avoid due to the nature of the commons project (my actual project contains up to 10 subprojects, and it should be possible to reuse the commons-project without declaring a dependency to sub-project-abstract every single time the commons-project is referenced.
Is there a way to make the Main-class aware of AbstractFoo without directly declaring sub-project-abstract as a dependency (but indirectly via sub-project-commons)?
This is expected behavior for the implementation configuration. You should apply the Java Library Plugin and use the api configuration.
The key difference between the standard Java plugin and the Java Library plugin is that the latter introduces the concept of an API exposed to consumers. A library is a Java component meant to be consumed by other components. It’s a very common use case in multi-project builds [emphasis added], but also as soon as you have external dependencies.
The plugin exposes two configurations that can be used to declare dependencies: api and implementation. The api configuration should be used to declare dependencies which are exported by the library API, whereas the implementation configuration should be used to declare dependencies which are internal to the component.
[...]
Dependencies appearing in the api configurations will be transitively exposed to consumers of the library, and as such will appear on the compile classpath of consumers. Dependencies found in the implementation configuration will, on the other hand, not be exposed to consumers, and therefore not leak into the consumers' compile classpath. [...]
In sub-project-commons (Kotlin DSL):
plugins {
...
`java-library`
}
...
dependencies {
api(project(":sub-project-abstract"))
}
...
Micronaut documentation says:
For test resources which make use of Testcontainers, you may extend
the base AbstractTestContainersProvider class.
My question is: how to add this class properly to the classpath of the test resources sourceset (I am using Gradle)
You will need to add the following dependencies to your build.gradle file:
dependencies {
testResourcesImplementation platform("io.micronaut:micronaut-bom:3.6.1")
testResourcesImplementation "io.micronaut.testresources:micronaut-test-resources-testcontainers"
}
(note that I'm importing the Micronaut BOM so that you don't have to specify the test resources version, but you could use it directly)
Within a Gradle multi-module project with the bootstrapping in its own module I'm unable to use MockMvc, because its need to reference the bootstrapping-module. I'm not sure if I have misconfigured something. The basic structure is:
module: a module containing some REST-Services and needs a testImplementation-Dependency on starter
starter: the bootstrapping-module which gets the spring-boot-plugin applied and depends on module
I have set up a minimal example on github using Spring-Boot 2.3.1.RELEASE and Gradle 6.4 with the following configuration:
./settings.gradle.kts
rootProject.name = "spring-multimodule-integrationtest"
include("starter", "module")
./build.gradle.kts
subprojects {
repositories {
jcenter()
}
dependencies {
apply(plugin = "java-library")
"testImplementation"("junit:junit:4.12")
}
}
./starter/build.gradle.kts
plugins {
id("org.springframework.boot") version "2.3.1.RELEASE"
}
dependencies {
implementation(project(":module"))
}
./module/build.gradle.kts
dependencies {
testImplementation(project(":starter"))
}
The starter-module contains only one a single class "Starter" referencing the module-module:
public class Starter {
public String info() { return "starter"; }
public static void main(String[] args) {
System.out.println(new Starter().info() + " and " + new Module().info());
}
}
The module-module (*sigh I should have chosen a different name for this module) contains only this implemenation-class:
public class Module {
public String info() { return "module"; }
}
Additionally, the module-module has the following test-class doing the integration-test:
public class IntegrationTest
{
#Test public void testSomeLibraryMethod() {
final ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
Starter.main(new String[0]);
assertEquals("starter and module\n", out.toString());
}
}
This code runs fine until the applying of the spring-boot-plugin within "./starter/build.gradle.kts". When the tasks "clean test" issued on the shell I get:
❯ ./gradlew clean test
> Task :module:test FAILED
de.kramhal.multi.IntegrationTest > testSomeLibraryMethod FAILED
java.lang.NoClassDefFoundError at IntegrationTest.java:17
Caused by: java.lang.ClassNotFoundException at IntegrationTest.java:17
1 test completed, 1 failed
This problem does not occur, when tests are executed within the IDE (IntelliJ to be exact).
I already tried unsuccessfully to use the spring-dependency-management as suggested in this answer (as well as in several other answers).
What have I done wrong?
First off, I would recommend restructuring your project so you don't have cyclic dependencies. As it is now, in order to build starter, you need to build module. And in order to test module, you need to build starter. Gradle can do it, but it is usually a smell.
In terms of troubleshooting: when you get a test failure like this, look at the test report as that has the full stack trace. You should see that it complains that it can't find the Starter class (Caused by: java.lang.ClassNotFoundException: de.kramhal.multi.Starter), which is of cause in the starter module.
You mentioned the spring-dependency-management plugin, but that is only relevant for managing Maven dependencies, and not project dependencies like this. So it is not helpful here.
I am not entirely sure if this is Windows specific or not as I remember there were some discussions around performance a while back when having a lot of classes. But I believe the java-library plugin will look for jar files in other projects, and not the folder for compiled classes. This is a problem for you since the spring-boot plugin will by default disable the standard jar task and instead create "fat" a jar file through the bootJar task. Because you need both the fat jar for packaging the application to run stand-alone but also the normal jar for consuming it as a dependency, you need to do some tweaks to the starter project (Kotlin DSL):
tasks {
jar {
enabled = true
}
bootJar {
archiveClassifier.set("boot")
}
}
This will enable the normal jar file, but because the name will conflict with the one produced by the bootJar task, you need to rename one of them. I chose to rename the bootJar one.
I don't know why the test works for you in IntelliJ as that should, by default, delegate everything to Gradle. But maybe you have an old version, or done some manual configuration to let IntelliJ both compile and run your tests.
How can a task be associated to a specific dependency configuration?
If I look the 23.5. Dependency management (gradle java plugin official doc) section part, it states that, for example, compileTestJava task use testCompile configuration.
I just wanted to know how I could achieve that.
gradle is creating these configurations automatically;
if you define a sourceSet, a bunch of things gets created (by convention):
sourceSets {
thing
}
will define configurations: thingCompile, thingRuntime
tasks: compileThingJava, processThingResources, thingClasses
you might want to look at: gradle tasks --all and gradle dependencies
if you want to add dependencies to these configurations
the most preferable to use the generated ones
you may of course create your own configuration and extend from that: configurations { thingCompile.extendsFrom(myConfig) }
I am integrating Java Plugin Framework within a Spring based web application (XML-free).
Everything is fine, except for the dependency injection in plugin context
For instance I have a data source I would like to use in a plugin without having to go back to property files by using #Autowired like for the rest of the application
I cannot find a way to do this except by using getBean, which I read was not the best practise on this subject.
I also had a look at LogicalDoc but this project resorts to properties reloading which is not the correct solution for me as I want beans attributes modifications to be available without further glue.
Does anyone know of an existing open source project where both these environment are used ?
Not being able to inject dependencies in plugins, I finally added the following methods to mother class of all plugins
public void setContext(ApplicationContextProvider a_ctx) {
m_theContext = a_ctx;
}
public ApplicationContext getApplicationContext() {
return m_theContext.getApplicationContext();
}
And retreive the beans from within the plugin by
getApplicationContext().getBean(*ClassType*)