Skip to content
This repository has been archived by the owner on Sep 13, 2024. It is now read-only.

Custom Guice modules in plugins are not registered in the plexus container, documentation is wrong #35

Closed
HomeOfTheWizard opened this issue May 8, 2023 · 33 comments

Comments

@HomeOfTheWizard
Copy link
Contributor

Hello,

Not sure what I am doing wrong but I cannot load a custome Guice/Sisu module to inject cutom bindings as explained in the documentation
https://github.com/eclipse/sisu.plexus/wiki/Plexus-to-JSR330#custom-bindings

I am using maven 3.9.1
and developping with OpenJDK 17.

Here is my pom.xml

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.release>17</maven.compiler.release>
    </properties>

    <dependencies>

        <!-- Maven dependencies for plugin development and sisu components -->
        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-core</artifactId>
            <version>3.9.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.maven</groupId>
            <artifactId>maven-plugin-api</artifactId>
            <version>3.9.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>org.apache.maven.plugin-tools</groupId>
            <artifactId>maven-plugin-annotations</artifactId>
            <version>3.8.1</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-plugin-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <skipErrorNoDescriptorsFound>true</skipErrorNoDescriptorsFound>
                </configuration>
                <executions>
                    <execution>
                        <id>mojo-descriptor</id>
                        <goals>
                            <goal>descriptor</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>help-goal</id>
                        <goals>
                            <goal>helpmojo</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.eclipse.sisu</groupId>
                <artifactId>sisu-maven-plugin</artifactId>
                <version>0.9.0.M1</version>
                <executions>
                    <execution>
                        <id>generate-index</id>
                        <goals>
                            <goal>main-index</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build> 

Here is my Mojo:

import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.codehaus.plexus.logging.Logger;

import javax.inject.Inject;

@Mojo( name = "sayhi")
public class MyMojo extends AbstractMojo
{
    @Inject
    private Logger logger;

    private MyComponent component;

    @Inject
    public MyMojo(MyComponent component)
    {
        this.component = component;
    }

    public void execute()
    {
        logger.info("executing mojo");
        try {
            component.hello();
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
    }
}

Here is my Module with custom bindings:

import com.google.inject.AbstractModule;
import javax.inject.Named;

@Named
public class MyModule extends AbstractModule {

    @Override
    public void configure() {
        bind(MyComponent.class).toInstance(new MyComponent());
    }
}

Can you please help me sort this out ?

@cstamas
Copy link
Member

cstamas commented May 8, 2023

What bytecode is your output? Maven 3.9.1 uses Sisu 0.3.5 that supports only up to Java 11 bytecode, anything above that will be ignored by Sisu.

@cstamas
Copy link
Member

cstamas commented May 8, 2023

If all good (re bytecode) you still should not mix Mojo annotations/API and JSR330. Fix for your issue should be:

  • add @Inject to the field private MyComponent component;
  • remove injected ctor completely

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 8, 2023

Thanks for the tips and the quick reply ! 👍
My issue actually was in the component that I tried to inject.
I was trying to bind a spring managed bean as instance of MyComponent.class.
The example above with a basic instance created with the constructor works actually.
I will try to figure out how to bind a spring managed instance.

@cstamas
Copy link
Member

cstamas commented May 8, 2023

And one more hint: ift you do not depend on Guice, just by using this single Module class, then instead of module just create:

@Singleton
@Named
public class MyComponentProvider implements javax.inject.Provider<MyCoomponent> {
  public MyComponent get() {
    ...construct my component
  }
}

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 8, 2023

Thanks again, this can be very useful!
I am actually trying to bind multiple beans from a library.
I will give a try to the spring-guice project, this may help me bind all beans in a spring container in one go.
If it does not work I will create a provider for all as you suggested

@HomeOfTheWizard
Copy link
Contributor Author

@cstamas one last question before I switch the subject, custom modules annotated with @Named are injected in Guice with the default no args constructor right ?
I am asking the question cause by looking quickly to the documentation of the spring-guice project, I will have to use the constructor to create my custom module that will bridge my Spring beans with Guice.

@cstamas
Copy link
Member

cstamas commented May 8, 2023

Yes, they must have default ctor

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 9, 2023

I have the same issue with the bean injected by the SpringModule class of spring-guice project.

Here is how I autocreate the module:

@Named
public class MySpringModule extends SpringModule {

    public MySpringModule(){
        super(new AnnotationConfigApplicationContext(MySpringConfig.class));
    }
}

The beans does not get injected as Guice component in my Mojo.

Even when I try to inject a spring managed bean myself in a custome Module like below, it does not work.

...
public void configure() {
        applicationContext = new AnnotationConfigApplicationContext(MySpringConfig.class); 
        var mySpringBean = applicationContext.getBean(MySpringBean.class);
        bind(MySpringBean.class).toInstance(mySpringBean);
        bind(MyComponent.class).toInstance(new MyComponent());
}
...

The normal component with ctor is injected, but not the spring managed instance.

However if I try to instantiate the spring container in a Guice component itself, get the bean and use it, it works fine.

@Named
@Singleton
public class MyComponent {

    private ApplicationContext applicationContext;

    private void displayAllBeans() {
        String[] allBeanNames = applicationContext.getBeanDefinitionNames();
        for(String beanName : allBeanNames) {
            System.out.println(beanName);
        }
    }

    public void sayHelloFromSpringBean(){
        var mySpringBean = applicationContext.getBean(MySpringBean.class);
        mySpringBean.sayHello();
    }

   public void hello() throws ClassNotFoundException {
        System.out.println( "Hello! I am a component that is being injected with sisu" );

        applicationContext = new AnnotationConfigApplicationContext(MySpringConfig.class);
        System.out.println( "Spring container initiated" );

        displayAllBeans();
        System.out.println( "Beans listed" );

        sayHelloFromSpringBean();
    }
}

Is there a difference between Sisu and Guice that can cause this issue ?
I applogies but I am new to guice, do you have any tips that can help me dig and see why it is not injected ?

@HomeOfTheWizard HomeOfTheWizard changed the title Cannot load Custom bindings Cannot load Custom binding for spring managed instance May 10, 2023
@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 13, 2023

@cstamas I have downgraded to JDK 11, removed the ctor and used inject in the field itself.
But nothing changed 😢
My basic java instance is injected but not the spring managed instance.

@cstamas
Copy link
Member

cstamas commented May 13, 2023

Am not a spring guy, but best would be if you create a reproducer in some shared repo, so i can look. Nothing guaranteed

@mcculls
Copy link
Contributor

mcculls commented May 13, 2023

Are you doing this in a plugin or extension? IIRC Maven doesn't expose the Guice API to plugins, so you'll need to stick with the javax.inject API using approaches like a writing a @Named Provider to delegate to Spring.

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 17, 2023

@cstamas
I fail to use a guice module. I managed to inject a simple POJO in sisu but not an instance binded by my custom module.

I would like to debug to see what happens but I couldn't do it with mvnDebug
It doesn't stop on the break points I put in the modules.
It looks like it listens for a debugger only once all the modules are created and the code of the mojo is ready to be executed.

I also tried to do some logging, but it also fails to print any output during modules initialisation.
Any System.out.println is printed.
I also tried to use the plexus logger org.codehaus.plexus.logging.Logger, but got the same result as with system.out.println

Is there a way to debug the initialisation of the modules in sisu ?
Sorry for such basic questions, I am new to maven development.

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 18, 2023

Just for your information,
To be sure that there is nothing wrong with my code, library and configs, I tested the same in an application explained as in this documentation
https://eclipse.github.io/sisu.inject/#ready

It worked fine. I managed to bind the bean coming from the spring library.
Here is the repo of the code that works.
https://github.com/HomeOfTheWizard/sisu-spring-test

But I just cant make it work when I package them all in a plugin and run inside maven.
Here is the repo of the plugin that cannot bind the same bean as the app above.
https://github.com/HomeOfTheWizard/spring-bridge-demo-maven-plugin

@mcculls
Copy link
Contributor

mcculls commented May 18, 2023 via email

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 26, 2023

@mcculls I tried to see if anything changes with an extention instead of a plugin, but it failed with same result.
I am starting to loose all hope here :'(
It would be greate if you can help me see how to debug the modules loaded by plugin/extension.

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 28, 2023

@mcculls ok! sorry! I just understood what you mean :)
you were talking about why I cannot debug the modules.
And actually you were right from the beggining! my modules are not picked up at all !

My POJOs that I inject in my MOJO are actually not binded by my custome modules!
Apparently the sisu can inject simple POJOs with default ctors, without any annotation on the componenet, and without an explicit binding ! interesting.

Now I am wondering what am I doing wrong, because my custome modules dont' work at all,
doesnt matter if I create them in a plugin or extension.
I am doing exactly what is shown here though.
https://github.com/eclipse/sisu.plexus/wiki/Plexus-to-JSR330#custom-bindings

Which is weird is that, I see MySpringModule as a component in the plexus container when I lookup with a LifeCycleParticipant
as shown here
https://maven.apache.org/studies/extension-demo/xref/
I simply do
session.getContainer().lookup(AbstractModule.class,"mySpringModule")
And it seems to have been picked up by sisu. But any System.out.println I put inside, do not show anything on console...

@mcculls
Copy link
Contributor

mcculls commented May 29, 2023

No worries - I wondered if it might be using the default constructors.

I'll need to confirm, but I think the issue is that Maven doesn't currently expose the Guice module API - so while you can use modules in your plugin, and query them via the lookup API, classes like AbstractModule.class are actually different to classes with the same name inside Maven core. (The class-loader mechanism in Maven means that you can load classes with the same name in the core and different plugins and they are actually different classes - only those explicitly exposed from the core will be the same across the core and all plugins.)

This means that Sisu, which lives inside core, cannot cast your module instance to its AbstractModule class - because the AbstractModule class loaded by your plugin is not the same as the AbstractModule class loaded by Maven core.

One approach would be to create a Guice injector inside your plugin and have JSR330 annotated Providers delegate to that shared injector instance.

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 29, 2023

No worries - I wondered if it might be using the default constructors.

I'll need to confirm, but I think the issue is that Maven doesn't currently expose the Guice module API - so while you can use modules in your plugin, and query them via the lookup API, classes like AbstractModule.class are actually different to classes with the same name inside Maven core. (The class-loader mechanism in Maven means that you can load classes with the same name in the core and different plugins and they are actually different classes - only those explicitly exposed from the core will be the same across the core and all plugins.)

This means that Sisu, which lives inside core, cannot cast your module instance to its AbstractModule class - because the AbstractModule class loaded by your plugin is not the same as the AbstractModule class loaded by Maven core.

One approach would be to create a Guice injector inside your plugin and have JSR330 annotated Providers delegate to that shared injector instance.

@mcculls Ok! Thanks! it is much clearer now :)
So the only way to inject objects into my plugin is to use a @Named Component or a javax.inject.Provider ?

Also just to be sure to understand:
What is shown in this example
https://github.com/eclipse/sisu.plexus/wiki/Plexus-to-JSR330#custom-bindings
is to show a way to add custom bindings to maven core, but not to the plugin's injection mechanism ?

@mcculls
Copy link
Contributor

mcculls commented May 29, 2023 via email

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented May 30, 2023 via email

@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented Jun 2, 2023

@mcculls I tried to use modules in an extension but couldt make it work either.
https://github.com/HomeOfTheWizard/demo-maven-extension.
When I lookup in the container, I see the module is picked up, but cannot find the component that I bind with the module.
Maybe my way of looking for the binded object is wrong ?
Here is what I do

session.getContainer().lookup(AbstractModule.class,"myModule"); //gets MyModule.class
session.getContainer().hasComponent("myComponent"); // return false.

BTW the session.getContainer() is deprecated. Maybe there is a better API to search in the sisu container ?

@HomeOfTheWizard HomeOfTheWizard changed the title Cannot load Custom binding for spring managed instance Cannot load Custom binding with Guice modules Jun 29, 2023
@HomeOfTheWizard
Copy link
Contributor Author

I will create another issue for the extention and keep this issue for waiting your confirmation on the usage of custom bindings in plugins

@dsyer
Copy link

dsyer commented Jul 2, 2023

I too would like to be able to use custom bindings in plugins. The documentation is either misleading or plain wrong. I have managed to debug a plugin execution, and I can't see any code anywhere that ever would register a custom Module even if the classpath was sorted out. The closest I could see was the QualifiedTypeBinder which is used by PlexusTypeBinder but is never called by the plugin system during my custom plugin execution.

UPDATE: after some more debugging I can see that the PlexusTypeBinder is not called because (as surmised above) the custom Module class cannot be loaded. If I explicitly add Guice to the classpath of my plugin then the binder is called but then doesn't bind anything because (as surmised above) the custom Module is not of type Module (different class loaders). This seems like a bug to me. Sisu obviously expects it to work and Maven and/or sisu.plexus has switched it off.

@HomeOfTheWizard
Copy link
Contributor Author

@dsyer thanks for the analysis, I wanted to debug as well but didnt have time.
That confirms what stuart was saying previously: It must have been switched off on purpose.
That leaves us with a single choice, doing the bridge with an with a maven extention ?
@mcculls I can try to work out a PR if you are willing to accept to open custom modules for plugins

@dsyer
Copy link

dsyer commented Jul 3, 2023

I think opening custom modules for plugins using the available sisu.inject mechanisms would be very dangerous. There's a reason the class loader boundaries are so strict - it's what keeps Maven plugins vaguely sane and working in spite of big changes in the Maven project infrastructure.

So I think we have to ask ourselves, what is it we actually need here? I think for me it is 2 things: 1) make my Mojo more testable by leaving it open to manual wiring, 2) re-use existing custom bindings from libraries.

The best I could come up with was to include Guice on the plugin classpath and create an Injector manually. You don't get to contribute custom bindings to the surrounding Plexus container, but you can inject stuff from it, e.g.

@Named
class InjectorProvider implements Provider<Injector> {
	private final Injector injector;

	@Inject
	InjectorProvider(MavenSession session) {
		injector = Guice.createInjector(new MyModule());
		// ... do stuff with MavenSession etc.
	}

	public Injector get() {
		return injector;
	}
}

Then inject that Injector either directly into your Mojo or (better but more verbose) create a Provider for each of the components you want, e.g.

@Named
class ServiceProvider implements Provider<Service> {
	private final Injector injector;

	@Inject
	ServiceProvider(Injector injector) {
		this.injector = injector;
	}

	public Service get() {
		return injector.getInstance(Service.class);
	}
}

This makes it explicit that you don't expect all the bindings in your library to end up in the plugin Plexus container, which I think is probably a good thing. It's a bit unfortunate that you have to write boilerplate code to expose the components that you need in your Mojo, so I would be prepared to live with a solution that made them all available. What would make this a lot nicer would be if we could get rid of the boilerplate (without opening the class loader boundaries). I don't know if that's do-able - probably it would involve a small helper library that you need on your plugin classpath. I might play around with that idea a bit.

@HomeOfTheWizard
Copy link
Contributor Author

I agree with that.
I will try to make a library for the boilerplate code for using modules in plugins.

What about an extention ? is it acceptable to delegate the responsability of not breaking everything in plexus to a developper of a maven extention and allow him/she to add custom bindings?
I am refering to that thicket
https://github.com/HomeOfTheWizard/demo-maven-extension

if so we can as well allow plugins use existing bindings from external libraries.

@dsyer
Copy link

dsyer commented Jul 3, 2023

I don’t know if an extension helps. The class loader boundary is constructed differently I think, but the isolation is just as strict. Happy to be wrong.

Anyway, I have a proof of concept for a library that creates a new injector like in the code above. I’ll play around with it some more and see if it’s worth publishing.

@HomeOfTheWizard
Copy link
Contributor Author

if you accept help I would love to contribute ^_^

@dsyer
Copy link

dsyer commented Jul 4, 2023

I put the code here: https://github.com/scratches/plugin-demo/.

@HomeOfTheWizard
Copy link
Contributor Author

Ok I see that you used a plugin as a dependency of the first plugin to generate automatically the providers, I was trying to do the same during launch break, but you were faster :D
I think you are almost done so I will let you continue.
Anyway thanks a lot.
I dont know what to do about this ticket however. should I just close it ?

@dsyer
Copy link

dsyer commented Jul 5, 2023

I think this issue should be fixed as a documentation issue at the very least, so I would leave it open. Maybe change the summary?

@HomeOfTheWizard HomeOfTheWizard changed the title Cannot load Custom binding with Guice modules Custom Guice modules in plugins are not registered in the plexus container, documentation is wrong Jul 5, 2023
@HomeOfTheWizard
Copy link
Contributor Author

HomeOfTheWizard commented Sep 29, 2023

Since @cstamas had initially proposed to use the Provider interface, I had started to play with it as well.
I managed to create a configurable plugin that is generic enough to serve a more general purpose.
https://github.com/HomeOfTheWizard/spring-bridge-maven-plugin
My goal was to have the spring-cloud vault library in my Vault Maven Plugin.
It can be configured to use any spring context configuration from any spring library, and doesn't require building the plugin.

However I still prefer using an extention and use the Spring-Guice project, So I tried to create a configurable extension.
https://github.com/HomeOfTheWizard/spring-bridge-maven-extension.
It has some bugs but nothing that cannot we worked around in my opinion.
@dsyer if you are interested to release a plugin and accept a modest help from a newbe, I can create a PR on your scratch project to make it configurable.

Also, @mcculls , I would like to contribute to fix the documentation.
However the doc is a github wiki so I cannot do a PR.
Would you be ok if we create github page documentation on this repo and move the wiki page to the github page ?
If yes I can create a PR for that as well.

@mcculls
Copy link
Contributor

mcculls commented Sep 30, 2023

@HomeOfTheWizard thanks for all your work on this - yes, moving the doc to a github page is fine with me

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants