Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NPE when injecting EntityManager/EntityManagerFactory into ObjectMapperCustomizer #9049

Closed
HonoluluHenk opened this issue May 4, 2020 · 22 comments
Labels
kind/bug Something isn't working triage/needs-feedback We are waiting for feedback.

Comments

@HonoluluHenk
Copy link

Describe the bug
I implemented the Jackson ObjectMapperCustomizer and tried to @Inject the Hibernate EntityManager or EntityManagerFactory.
On quarkus:dev startup, a NullPointerException gets thrown:

Caused by: java.lang.NullPointerException
	at io.quarkus.agroal.runtime.AbstractDataSourceProducer.getDataSourceBuildTimeConfig(AbstractDataSourceProducer.java:375)

Expected behavior
Application should start, EM/EMF should get injected.

Actual behavior
NPE, see description

To Reproduce
Steps to reproduce the behavior:

  1. Check out https://github.com/HonoluluHenk/quarkus-bug-jacksoncustomizer-hibernate.git
  2. ./mvnw clean test
  3. Watch it crash&burn

Configuration
N/A

Screenshots
N/A

Environment (please complete the following information):

  • Quarkus version or git rev: 1.4.1.Final

Additional context
Most specific stacktrace entry:

Caused by: java.lang.NullPointerException
        at io.quarkus.agroal.runtime.AbstractDataSourceProducer.getDataSourceBuildTimeConfig(AbstractDataSourceProducer.java:375)
        at io.quarkus.agroal.runtime.DataSourceProducer.createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595(DataSourceProducer.zig:31)
        at io.quarkus.agroal.runtime.DataSourceProducer_ProducerMethod_createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595_5923a8feaf2f425c7e2697f04d550dce98568e14_Bean.create(DataSourceProducer_ProducerMethod_createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595_5923a8feaf2f425c7e2697f04d550dce98568e14_Bean.zig:354)
        at io.quarkus.agroal.runtime.DataSourceProducer_ProducerMethod_createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595_5923a8feaf2f425c7e2697f04d550dce98568e14_Bean.create(DataSourceProducer_ProducerMethod_createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595_5923a8feaf2f425c7e2697f04d550dce98568e14_Bean.zig:297)
        at io.quarkus.arc.impl.AbstractSharedContext.createInstanceHandle(AbstractSharedContext.java:79)
        at io.quarkus.arc.impl.ComputingCache$CacheFunction.lambda$apply$0(ComputingCache.java:99)
        at io.quarkus.arc.impl.LazyValue.get(LazyValue.java:26)
        at io.quarkus.arc.impl.ComputingCache.getValue(ComputingCache.java:41)
        at io.quarkus.arc.impl.AbstractSharedContext.get(AbstractSharedContext.java:25)
        at io.quarkus.agroal.runtime.DataSourceProducer_ProducerMethod_createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595_5923a8feaf2f425c7e2697f04d550dce98568e14_Bean.get(DataSourceProducer_ProducerMethod_createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595_5923a8feaf2f425c7e2697f04d550dce98568e14_Bean.zig:269)
        at io.quarkus.agroal.runtime.DataSourceProducer_ProducerMethod_createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595_5923a8feaf2f425c7e2697f04d550dce98568e14_Bean.get(DataSourceProducer_ProducerMethod_createDataSource_4c279770c59fa93dcf179ab15a1363f01f14f595_5923a8feaf2f425c7e2697f04d550dce98568e14_Bean.zig:41)
        at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:380)
        at io.quarkus.arc.impl.ArcContainerImpl.beanInstanceHandle(ArcContainerImpl.java:393)
        at io.quarkus.arc.impl.ArcContainerImpl.instanceHandle(ArcContainerImpl.java:363)
        at io.quarkus.arc.impl.ArcContainerImpl.instance(ArcContainerImpl.java:200)
        at io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.injectDataSource(FastBootHibernatePersistenceProvider.java:265)
        at io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.getEntityManagerFactoryBuilderOrNull(FastBootHibernatePersistenceProvider.java:155)
        at io.quarkus.hibernate.orm.runtime.FastBootHibernatePersistenceProvider.createEntityManagerFactory(FastBootHibernatePersistenceProvider.java:48)
        at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:80)
        at javax.persistence.Persistence.createEntityManagerFactory(Persistence.java:55)
        at io.quarkus.hibernate.orm.runtime.JPAConfig$LazyPersistenceUnit.get(JPAConfig.java:110)
        at io.quarkus.hibernate.orm.runtime.JPAConfig.getEntityManagerFactory(JPAConfig.java:43)
        at io.quarkus.hibernate.orm.runtime.JPAResourceReferenceProvider.lambda$get$0(JPAResourceReferenceProvider.java:26)
        at io.quarkus.arc.impl.ResourceProvider.get(ResourceProvider.java:36)
        at io.quarkus.hibernate.orm.runtime.DefaultEntityManagerFactoryProducer_Bean.create(DefaultEntityManagerFactoryProducer_Bean.zig:61)
        ... 78 more

@HonoluluHenk HonoluluHenk added the kind/bug Something isn't working label May 4, 2020
@machi1990
Copy link
Member

/cc @gsmet @geoand

@geoand
Copy link
Contributor

geoand commented May 4, 2020

Unfortunately this can't reall work as expected due to timing issues.

What exactly are you trying to do? Asking because it might be something we can do inside the Jackson extension (by providing optional integration with the hibernate extension).

@HonoluluHenk
Copy link
Author

I'm trying to implement some Deserializer/BeanDeserializerModifier for a bunch of entities.

In my case: entities that implement a certain interface.
So i'm querying the EMF for all entities, loop over the ones that implement my interface and register the converters in a jackson module.

Pseudocode:

for (entity = emf.getEntities()) {
  module.addDeserializer(entity.getClass(), new MyDeserializer(entity.getClass));
}

@geoand
Copy link
Contributor

geoand commented May 12, 2020

I see, so all you need are the entity classes, right?

@HonoluluHenk
Copy link
Author

entities (and for future use) maybe everything that's available via MetaModel

@geoand
Copy link
Contributor

geoand commented May 14, 2020

@Sanne is there any way that an arbitrary piece of Quarkus code could find the entities found at build time?

Obviously it's possible, so my question is more whether we have anything like that in place

@Sanne
Copy link
Member

Sanne commented May 15, 2020

We don't create the EntityManager producer when there are no entities.

I believe a way to find them is to depend on the buildItem io.quarkus.hibernate.orm.deployment.JpaEntitiesBuildItem - however be careful with the other phases, as while you're safe to have a list of names, the class definitions themselves might still need transformations.

@Sanne
Copy link
Member

Sanne commented May 15, 2020

ah sorry it appears I misunderstood the question. You don't want to have the list at build time :)

entities (and for future use) maybe everything that's available via MetaModel

Yes that's correct; however to have an EntityManager started requires having a datasource to be configured as well.

@geoand
Copy link
Contributor

geoand commented May 15, 2020

For the time being, just being able to grab the list of entities (their classes to be precise) from arbitrary code would be enough I think.
Do we have some easy way to do that?

@Sanne
Copy link
Member

Sanne commented May 15, 2020

But do you need it at build time or runtime? If runtime, what do you expect to see as API?

@geoand
Copy link
Contributor

geoand commented May 15, 2020

In this case, the OP needs them at runtime in order to customize Jackson.

But I was also wondering if it made sense for us to know them at build so we can enable some kind of customization of Jackson ourselves if necessary.

@Sanne
Copy link
Member

Sanne commented May 15, 2020

You can get them at build time easily as there are BuildItems which list them; this is also the contract used between Panache and ORM.

Regarding Jackson, I'd say the ideal solution is to also generate this all at build time. If that's too complicated, perhaps you can use some ArC API to see if you can get an EntityManager ?

If there is no entity manager factory, then there are no managed entities - which is what I suppose matters here? Of course you might still miss to find classes annotated with @Entity, but the annotation alone in such case would have no impact on the application.

@Sanne
Copy link
Member

Sanne commented May 15, 2020

The build iteams which might be relevant are JpaEntitiesBuildItem and JpaModelIndexBuildItem

@geoand
Copy link
Contributor

geoand commented May 15, 2020

Thanks @Sanne

@geoand
Copy link
Contributor

geoand commented May 21, 2020

@HonoluluHenk How does this design sound:

Quarkus would provide an AbstractEntityClassObjectMapperCustomizer which will give you access to the entity classes (via a protected final method).
All you would have to do to use is it subclass, implement an abstract method with your logic it and make it a bean (by for example annotating it with @Singleton).

What do you think?

@HonoluluHenk
Copy link
Author

yeah, that'd solve my problem.

Problem I foresee with this solution: if another usecase arises where you not only need Hibernate entities but also some other resources from other extensions (of which we do not yet have an idea yet), you'd need an AbstractOtherStuffObjectMapperCustomizer.... and then lateron some AbstractMoreStuffObjectMapperCustomizer.... ad infimum. But: the Application only may extend one of these.

One idea to remedy this (but I have no idea if that's possible!): Quarkus provides some buildtime-injectable bean (like e.g. QuarkusHibernateEntityEnumerator) that could be @Injected into the exising ObjectMapperCustomizer (or maybe anywhere else?).
That might solve the problem not only for Jackson but for other extensions doing build-time-magic as well... (Hibernate Search is one of these that comes to mind) and would be (imho) quite intuitive to use since I'd just have to @Inject something.
Another plus: this could be fairly easily documented on the page of the actual extension providing this feature (here: hibernate) since with this implementation it'd be independend of where I want to to use it.

Usage might then look like this:

@Singleton
public class RegisterCustomModuleCustomizer implements ObjectMapperCustomizer {

    @Inject
    QuarkusHibernateEntityEnumerator entityEnumerator;

    public void customize(ObjectMapper mapper) {
        doMyThing(entityEnumerator.enumerateEntities(), mapper);
    }
}

Another idea this time building on your implementation, tryingo to remedy the aforementioned problem:
Define some marker interface: (e.g. QuarkusBuildtimeInfo) and have an ObjectMapperCustomizer like this:

public abstract class AbstractObjectMapperCustomizer implements ObjectMapperCustomizer {

    protected final <T extends QuarkusBuildtimeInfo> Optional<T> findBuildtimeInfo(Class<? extends T> implementation) {
            ...
    }
}

and then - for now the only implementation - the Hibernate extension e.g.:

interface HibernateBuildtimeInfo extends QuarkusBuildtimeInfo {

    Set<Class<?>> getEntityClasses();
}

I could then use this construct as follows:

@Singleton
public class MyCystomizer extends AbstractObjectMapperCustomizer {

    @Override
    public void customize(ObjectMapper mapper) {
        findBuildtimeInfo(HibernateBuildtimeInfo.class)
            .map(HibernateBuildtimeInfo::getEntityClasses)
            .map(entityClasses -> doMyThing(entityClasses, mapper).
            .orElseThrow(() -> new IllegalStateException("HibernateBuildtimeInfo not found?"));
    }
}

@geoand
Copy link
Contributor

geoand commented May 22, 2020

I like the first solution a lot, it's very straightforward!
When I have some spare cycles I'll look into it, but I think it should be possible.

@HonoluluHenk
Copy link
Author

@geoand I just looked into your commits and like them alot 👍

Imho the 99% use case is to use the Entity classes, not their stringified class name.
So: would it be possible to have a Set<Class<?>> instead of Set<String>?

@geoand
Copy link
Contributor

geoand commented May 26, 2020

@HonoluluHenk that sounds reasonable, but I am hesitant to do for a couple of reasons:

  1. I don't want to load the class eagerly if no bean ever uses it
  2. Doing it lazily would mean that we would have to have synchronization code to populate the set of classes.

So I think it's best left up to client code to load the classes (via the Thread Context ClassLoader)

@HonoluluHenk
Copy link
Author

I created PR geoand#5 to care for these 2 problems...
I'm using ConcurrentHashMap as a poor mans (but imho working and correct) implementation of lazy loading with locking.
What do you think?

@geoand
Copy link
Contributor

geoand commented Feb 27, 2024

Is this still an issue?

@geoand geoand added the triage/needs-feedback We are waiting for feedback. label Feb 27, 2024
@geoand
Copy link
Contributor

geoand commented Apr 2, 2024

Closing for lack of feedback

@geoand geoand closed this as not planned Won't fix, can't repro, duplicate, stale Apr 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug Something isn't working triage/needs-feedback We are waiting for feedback.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants