BeanManager createInstance being called before AfterBeanDiscovery event fired


#1

We are using Hibernate ORM as the JPA persistence provider in the WebSphere Application Server 19.0.0.1 (Libertry), along with Hibernate Search. WebSphere Liberty CDI 2.0 is based on Weld.

During startup of the container, Hibernate Search tries to use the BeanManager prematurely, before the container has fired the AfterBeanDiscovery event, in relation to creating FieldBridge instances.

This behavior is in violation of the Java EE 8 Specification for the BeanManager interface, which states that the createInstance method must not be called before AfterBeanDiscovery event is fired.

Here is the stack trace:

org.hibernate.search.exception.SearchException: HSEARCH000139: Unable to instantiate FieldBridge
at org.hibernate.search.bridge.impl.BridgeFactory.createFieldBridgeFromAnnotation(BridgeFactory.java:400)
at org.hibernate.search.bridge.impl.BridgeFactory.findExplicitFieldBridge(BridgeFactory.java:350)
at org.hibernate.search.bridge.impl.BridgeFactory.buildFieldBridge(BridgeFactory.java:203)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.bindFieldAnnotation(AnnotationMetadataProvider.java:1335)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.checkForField(AnnotationMetadataProvider.java:1258)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.initializeMemberLevelAnnotations(AnnotationMetadataProvider.java:1065)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.initializeClass(AnnotationMetadataProvider.java:599)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.checkForIndexedEmbedded(AnnotationMetadataProvider.java:1924)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.initializeMemberLevelAnnotations(AnnotationMetadataProvider.java:1074)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.initializeClass(AnnotationMetadataProvider.java:599)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.doGetTypeMetadataFor(AnnotationMetadataProvider.java:192)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.getTypeMetadataFor(AnnotationMetadataProvider.java:181)
at org.hibernate.search.engine.metadata.impl.AnnotationMetadataProvider.getTypeMetadataFor(AnnotationMetadataProvider.java:165)
at org.hibernate.search.spi.SearchIntegratorBuilder.initDocumentBuilders(SearchIntegratorBuilder.java:445)
at org.hibernate.search.spi.SearchIntegratorBuilder.createNewFactoryState(SearchIntegratorBuilder.java:244)
at org.hibernate.search.spi.SearchIntegratorBuilder.buildNewSearchFactory(SearchIntegratorBuilder.java:200)
at org.hibernate.search.spi.SearchIntegratorBuilder.buildSearchIntegrator(SearchIntegratorBuilder.java:128)
at org.hibernate.search.hcore.impl.HibernateSearchSessionFactoryObserver.boot(HibernateSearchSessionFactoryObserver.java:127)
at org.hibernate.search.hcore.impl.HibernateSearchSessionFactoryObserver.sessionFactoryCreated(HibernateSearchSessionFactoryObserver.java:94)
at org.hibernate.internal.SessionFactoryObserverChain.sessionFactoryCreated(SessionFactoryObserverChain.java:35)
at org.hibernate.internal.SessionFactoryImpl.(SessionFactoryImpl.java:371)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:462)
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:938)

Caused by: org.hibernate.resource.beans.container.internal.NotYetReadyException: CDI BeanManager not (yet) ready to use
resolveContainerInstance:160, ContainerManagedLifecycleStrategy$BeanImpl (org.hibernate.resource.beans.container.internal)
initialize:92, ContainerManagedLifecycleStrategy$AbstractBeanImpl (org.hibernate.resource.beans.container.internal)
createBean:43, CdiBeanContainerImmediateAccessImpl (org.hibernate.resource.beans.container.internal)
createBean:64, AbstractCdiBeanContainer (org.hibernate.resource.beans.container.spi)
getBean:38, AbstractCdiBeanContainer (org.hibernate.resource.beans.container.spi)
resolve:63, HibernateOrmBeanContainerBeanResolver (org.hibernate.search.cfg.impl)
resolve:40, ReflectionFallbackBeanResolver (org.hibernate.search.engine.service.beanresolver.impl)
createFieldBridgeFromAnnotation:371, BridgeFactory (org.hibernate.search.bridge.impl)
findExplicitFieldBridge:350, BridgeFactory (org.hibernate.search.bridge.impl)
buildFieldBridge:203, BridgeFactory (org.hibernate.search.bridge.impl)
bindFieldAnnotation:1335, AnnotationMetadataProvider (org.hibernate.search.engine.metadata.impl)
checkForField:1258, AnnotationMetadataProvider (org.hibernate.search.engine.metadata.impl)
initializeMemberLevelAnnotations:1065, AnnotationMetadataProvider (org.hibernate.search.engine.metadata.impl)
initializeClass:599, AnnotationMetadataProvider (org.hibernate.search.engine.metadata.impl)
doGetTypeMetadataFor:192, AnnotationMetadataProvider (org.hibernate.search.engine.metadata.impl)
getTypeMetadataFor:181, AnnotationMetadataProvider (org.hibernate.search.engine.metadata.impl)
getTypeMetadataFor:165, AnnotationMetadataProvider (org.hibernate.search.engine.metadata.impl)
initDocumentBuilders:445, SearchIntegratorBuilder (org.hibernate.search.spi)
createNewFactoryState:244, SearchIntegratorBuilder (org.hibernate.search.spi)
buildNewSearchFactory:200, SearchIntegratorBuilder (org.hibernate.search.spi)
buildSearchIntegrator:128, SearchIntegratorBuilder (org.hibernate.search.spi)
boot:127, HibernateSearchSessionFactoryObserver (org.hibernate.search.hcore.impl)
sessionFactoryCreated:94, HibernateSearchSessionFactoryObserver (org.hibernate.search.hcore.impl)
sessionFactoryCreated:35, SessionFactoryObserverChain (org.hibernate.internal)
:371, SessionFactoryImpl (org.hibernate.internal)
build:462, SessionFactoryBuilderImpl (org.hibernate.boot.internal)
build:938, EntityManagerFactoryBuilderImpl (org.hibernate.jpa.boot.internal)
createContainerEntityManagerFactory:141, HibernatePersistenceProvider (org.hibernate.jpa)
createEMFactory:910, JPAPUnitInfo (com.ibm.ws.jpa.management)
initialize:762, JPAPUnitInfo (com.ibm.ws.jpa.management)
extractPersistenceUnits:183, JPAPxmlInfo (com.ibm.ws.jpa.management)
processPersistenceUnit:89, JPAScopeInfo (com.ibm.ws.jpa.management)
addPersistenceUnits:119, JPAApplInfo (com.ibm.ws.jpa.management)
processEJBModulePersistenceXml:543, JPAComponentImpl (com.ibm.ws.jpa.container.osgi.internal)
applicationStarting:275, JPAComponentImpl (com.ibm.ws.jpa.container.osgi.internal)
fireStarting:28, ApplicationStateManager (com.ibm.ws.container.service.state.internal)
fireApplicationStarting:50, StateChangeServiceImpl (com.ibm.ws.container.service.state.internal)
preDeployApp:383, DeployedAppInfoBase (com.ibm.ws.app.manager.module.internal)
deployApp:412, DeployedAppInfoBase (com.ibm.ws.app.manager.module.internal)
install:76, EARApplicationHandlerImpl (com.ibm.ws.app.manager.ear.internal)
execute:140, StartAction (com.ibm.ws.app.manager.internal.statemachine)
enterState:1258, ApplicationStateMachineImpl (com.ibm.ws.app.manager.internal.statemachine)
run:873, ApplicationStateMachineImpl (com.ibm.ws.app.manager.internal.statemachine)
run:239, ExecutorServiceImpl$RunnableWrapper (com.ibm.ws.threading.internal)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)
Caused by: java.lang.IllegalStateException: java.lang.UnsupportedOperationException: No current bean manager found in CDI service
at com.ibm.ws.jpa.container.v21.cdi.internal.BeanManagerInvocationHandler.invoke(BeanManagerInvocationHandler.java:69)
at com.sun.proxy.$Proxy65.createInstance(Unknown Source)
at org.hibernate.resource.beans.container.internal.ContainerManagedLifecycleStrategy$BeanImpl.resolveContainerInstance(ContainerManagedLifecycleStrategy.java:155)
… 49 more
Caused by: java.lang.UnsupportedOperationException: No current bean manager found in CDI service
at com.ibm.ws.jpa.container.v21.cdi.internal.BeanManagerInvocationHandler.getTarget(BeanManagerInvocationHandler.java:78)
at com.ibm.ws.jpa.container.v21.cdi.internal.BeanManagerInvocationHandler.invoke(BeanManagerInvocationHandler.java:62)
… 51 more


#2

This is most likely a problem with how CDI is integrated with Hibernate ORM in Websphere.

You see, Hibernate Search does not have a choice about when it creates beans; it must create beans when it starts, and it starts when Hibernate ORM tells it to. Hibernate Search also doesn’t have a clue about the dependency injection technology being used under the hood (CDI, Spring, etc.): it just uses SPIs provided by Hibernate ORM.

In order for Hibernate Search to delay its start, it would need the CDI engine to send it an event at the appropriate time. Fortunately, there’s a mechanism just for that in the SPIs allowing to integrate a CDI engine into Hibernate ORM, and it already works in WildFly. However, in order for this mechanism to work, the application server must pass to Hibernate ORM a bean manager that implements a specific interface: org.hibernate.resource.beans.container.spi.ExtendedBeanManager.

If Hibernate ORM is built into Websphere, I would suggest you submit a ticket to them, requesting that the bean manager passed to Hibernate ORM through the javax.persistence.bean.manager property implements org.hibernate.resource.beans.container.spi.ExtendedBeanManager. They don’t have to implement the interface directly in their own CDI solution, they could just wrap the bean manager in another object that implements that interface, just like WildFly did.
See the javadoc of org.hibernate.cfg.AvailableSettings#CDI_BEAN_MANAGER for more information.

If Hibernate ORM is not built into Websphere, or if you need a quick hack to solve the problem in your application, you can always implement the “notification” mechanism from the CDI engine to Hibernate Search yourself. You will need to do three things:

  • Implement a org.hibernate.search.hcore.spi.EnvironmentSynchronizer
  • Make sure the synchronizer is made available to Hibernate Search through Hibernate ORM by implementing a org.hibernate.service.spi.ServiceContributor. You can find an example implementation here. Your implementation will be different, but that will give you an idea of what you can do.
  • Make sure the service contributor is detected by Hibernate ORM by adding a file named META-INF/services/org.hibernate.service.spi.ServiceContributor to your resources, and setting its content to a single line of text: the fully-qualified name of your service contributor class.

#3

Thank you very much for your reply. The problem has been solved successfully, after registering a CDI Extension with the container via META-INF/services/javax.enterprise.inject.spi.Extension to listen to the bean discovery events, and then wiring up the EnvironmentSynchronizer as you describe.

I’m not sure how much traction I will be able to achive with IBM if I ask them to make changes to their implementation of the spec, when we already have a way to simply abide by it… If Hibernate uses the BeanManager interface, shouldn’t it play by the rules, without any extra remediation or accommodation?

I am asking, how do I make the case to IBM that Hibernate is above the law? This document, which Gavin King wrote (among others), is very clear about when not to make calls to the BeanManager:

https://javaee.github.io/javaee-spec/javadocs/javax/enterprise/inject/spi/BeanManager.html

After reading this, it does seem like Hibernate is the proper place to listen to the bean discovery events from the container, since Hibernate is the one making use of the BeanManager API, both of which are constituents of the injection framework.

If there is a case to be made for doing this within the container, I’d like to understand that point of view so I can write a convincing case to IBM.

Thank you again, this formula is exactly what I needed to get our application up and running on the latest releases of Hibernate, Hibernate Search, and WebSphere Liberty.


#4

From what I understand, the extension was necessary in order to make things work; for some reason the CDI spec didn’t allow us to do what we wanted exactly.

@sebersole might be able to tell you more, I personally don’t really remember the details of why EntityManager had to be extended.

What I know is that, in the early days of the Hibernate Search/CDI experiments (when we were not relying on ORM to provide the CDI integration), Hibernate Search used to rely on a simple hack. It got more complicated in WildFly 11, and we started relying on the ExtendedBeanManager interface that was provided by Wildfly, but I think the idea stayed the same. Then ORM started to provide its own CDI integration and we switched to that in Hibernate Search.

Maybe that hack, or a cleaner solution, could be implemented in Hibernate ORM? If you want to give it a try, we certainly could have a look at a PR. I think @sebersole would be glad to get rid of ExtendedBeanManager too, if the alternative works just as well.


#5

It is a chicken-egg problem that is actually caused by the EE spec and its various sub-specs. At the most basic:

  1. CDI wants JPA initialized before it finishes its own initialization so that it can inject EntityManagerFactory references. This is according to the CDI spec.
  2. JPA wants CDI initialized so that it can consume CDI beans as “entity listeners”.

The EE spec, unfortunately, does nothing to dictate how this should work.

Now technically speaking the JPA spec does not explicitly say that CDI should be accessed on JPA bootstrap. To that end Hibernate does offer 2 additional options specifically aimed at alleviating this chicken-egg problem:

  1. Instead of passing an instance the container’s BeanManager impl (via the standard javax.persistence.bean.manager setting), pass an instance of org.hibernate.resource.beans.container.spi.ExtendedBeanManager.
  2. Set hibernate.delay_cdi_access to true.

Option (1) is, I think, what @yrodiere calls “a hack”. But really we do not consider it a hack per-se. We consider it a solution to the inherent problem in EE. In fact we (Red Hat) have requested something similar in all the EE-related specs. Really though, this option is meant more as a container-level concern. However, it has significant pros compared to…

Option (2) essentially tells Hibernate to delay accessing the BeanManager until it first needs to which is during runtime. In other words, the first time you perform an operation that needs a particular CDI bean Hibernate will ask the BeanManager for it. This has a serious downside in that if the bean does not exist you will not know about that until runtime, conceivably months after a deployment.

Because option (2) delays config/bootstrap errors until runtime, we actually consider this approach the hack. But generally speaking it is the easiest for users to use.

FWIW… CDI does have some form of “BeanManager is ready to use” callback already, but to be honest it is sh*t and does not work for cases like this. It is statically defined with no way to associate the triggered event back to the SessionFactory / EntityManagerFactory - we’d have to time travel back to the 1980’s and use thread-locals or some such to use that approach.

HTH


#6

Steve, thank you for your comments, that sheds quite a little light on the subject.

In our CDI extension, the javax.enterprise.inject.spi.AfterBeanDiscovery event is working well as the trigger to proceed with the boot(sessionFactory) process of Hibernate Search. This event comes before AfterDeploymentValidation.

Since IBM now owns RedHat, I have been wondering if EclipseLink will be replaced by Hibernate any time soon, as the default persistence provider of choice. It is also interesting to have Wildfly, WebSphere and WebSphere Liberty under the same roof so to speak.

According to one of the WebSphere/Liberty developers in Rochester, it was IBM who contributed the WebSphereLibertyJtaPlatform to Hibernate, so at least I can see the integration is on their mind, and they are willing to make things easier.

We do have a WebSphere support contract, and I plan to open a case at IBM per our conversation. Thank you, your help is much appreciated.