ManagedBeanRegistryImpl.getBean in 7.2.0 throwing exception

Upgraded recently to 7.1.11 and things worked fine. Now on 7.2.0 getting an error because of this

public <T> ManagedBean<T> getBean(Class<T> beanClass, BeanInstanceProducer fallbackBeanInstanceProducer) {
		final String beanClassName = beanClass.getName();
		final var existing = registrations.get( beanClassName );
		if ( existing != null ) {
			if ( !beanClass.equals( existing.getBeanClass() ) ) {
				throw new AssertionFailure( "Wrong type of bean: " + beanClassName );
			}

in ManagedBeanRegistryImpl. So, in our case beanClass is an interface and existing.getBeanClass() returns interface implementation class and then class names don’t match and we get an exception. Can this be changed to see if existing.getBeanClass extends beanClass?

Thanks

I guess that’s fine, but please try to create a reproducer with our test case template and if you are able to reproduce the issue, create a bug ticket in our issue tracker and attach that reproducer.

Hm, not so simple to create reproducer since it is part of the framework and we just started getting this error with 7.20.0. And I think suggested change (don’t check class equality but whether it is assignable) is reasonable.

Let’s see, I added a comment to the commit. Maybe @gavinking can elaborate why he did this changed in the first place.

not so simple to create reproducer since it is part of the framework

Statements like this trigger my spidey sense that the user is probably doing something a bit wrong somewhere. If you can’t create a reproducer, then for sure you don’t properly understand the problem.

Hi ,

Here is simplified test case that shows what is the issue we are having.
If it makes sense, I can attach it and create issue, if not then I won’t bother creating issue since I have added workaround in our code.

package org.hibernate.bugs;

import org.hibernate.resource.beans.container.spi.BeanContainer;
import org.hibernate.resource.beans.container.spi.ContainedBean;
import org.hibernate.resource.beans.internal.ManagedBeanRegistryImpl;
import org.hibernate.resource.beans.spi.BeanInstanceProducer;
import org.hibernate.resource.beans.spi.ManagedBean;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;

import static org.assertj.core.api.Assertions.assertThat;

/**
 * Test showing issue with ManagedBeanRegistryImpl when bean type is interface and actual bean
 * an implementation of interface.
 */
class ManagedBeanRegistryTest {

    @Test
    void getByNameAndContract_withCustomBeanContainer() {

       DummyApplicationContext dummyApplicationContext = new DummyApplicationContext();
       dummyApplicationContext.registerBean(Greeter.class, new GreeterImpl("dummy"));

       final TestBeanContainer container = new TestBeanContainer(dummyApplicationContext);

       final ManagedBeanRegistryImpl registry = new ManagedBeanRegistryImpl(container);
       assertThat(registry.getBeanContainer()).isSameAs(container);

       // Lookup by type - the registry delegates to the container
       final ManagedBean<? extends Greeter> managedBean = registry.getBean(Greeter.class);
       assertThat(managedBean).isNotNull();
       assertThat(managedBean.getBeanInstance().greet()).isEqualTo("dummy");

       // Another lookup by type will throw an error because of
       /*if ( !beanClass.equals( existing.getBeanClass() ) ) {
          throw new AssertionFailure( "Wrong type of bean: " + beanClassName );
       }*/
       // in ManagedBeanRegistryImpl
       final ManagedBean<Greeter> sameByBeanType = registry.getBean(Greeter.class);
       assertThat(sameByBeanType.getBeanInstance()).isSameAs(managedBean.getBeanInstance());
    }

    public interface Greeter {
       String greet();
    }

    public static class GreeterImpl implements Greeter {
       private final String msg;

       public GreeterImpl(String msg) {
          this.msg = msg;
       }

       @Override
       public String greet() {
          return msg;
       }
    }

    /**
     * Simplified application context that is used to register and retrieve beans.
     */
    public static class DummyApplicationContext {

       private static final String DEFAULT_NAME = "default";

       Map<Class<?>, Map<String, Object>> instances = new HashMap<>();

       <T> void registerBean(Class<T> beanType, T instance) {
          registerBean(beanType, DEFAULT_NAME, instance);
       }

       <T> void registerBean(Class<T> beanType, String name, T instance) {
          Map<String, Object> beanInstanceByName = instances.get(beanType);
          if (beanInstanceByName == null) {
             beanInstanceByName = new HashMap<>();
          }
          if (beanInstanceByName.containsKey(name)) {
             if (DEFAULT_NAME.equals(name)) {
                throw new IllegalStateException("Bean instance of type " + beanType + " already exists");
             }
             throw new IllegalStateException("Bean instance with name of type " + beanType + " named " + name + " already exists");
          }
          beanInstanceByName.put(name, instance);
          instances.put(beanType, beanInstanceByName);
       }

       <T> boolean containsBean(Class<T> beanType) {
          return containsBean(beanType, DEFAULT_NAME);
       }

       <T> boolean containsBean(Class<T> beanType, String name) {
          if (!instances.containsKey(beanType)) {
             return false;
          }
          Map<String, Object> beanInstanceByName = instances.get(beanType);
          return beanInstanceByName.containsKey(name);
       }

       <T> T getBean(Class<T> beanType) {
          return getBean(beanType, DEFAULT_NAME);
       }

       <T> T getBean(Class<T> beanType, String name) {
          Map<String, Object> beanInstanceByName = instances.get(beanType);
          if (beanInstanceByName == null) {
             throw new IllegalStateException("Bean instance of type " + beanType + " not found");
          }
          T instance = (T) beanInstanceByName.get(name);
          if (instance == null) {
             if (DEFAULT_NAME.equals(name)) {
                throw new IllegalStateException("Bean instance of type " + beanType + " not found");
             }
             throw new IllegalStateException("Bean instance of type " + beanType + " named " + name + " not found");
          }
          return instance;
       }
    }

    /**
     * Minimal BeanContainer to simulate "registration" of beans by type or by name.
     * It returns a simple ContainedBean wrapper around pre-registered instances,
     * or falls back to the given BeanInstanceProducer.
     */
    public static class TestBeanContainer implements BeanContainer {
       private final DummyApplicationContext applicationContext;

       public TestBeanContainer(DummyApplicationContext applicationContext) {
          this.applicationContext = applicationContext;
       }

       @Override
       public <B> ContainedBean<B> getBean(
             Class<B> beanType,
             LifecycleOptions lifecycleOptions,
             BeanInstanceProducer fallbackProducer) {
          final Object instance = applicationContext.containsBean(beanType)
                ? applicationContext.getBean(beanType)
                : fallbackProducer.produceBeanInstance(beanType);
          return new SimpleContainedBean<>(beanType.cast(instance));
       }

       @Override
       public <B> ContainedBean<B> getBean(
             String name,
             Class<B> beanType,
             LifecycleOptions lifecycleOptions,
             BeanInstanceProducer fallbackProducer) {
          final Object instance = applicationContext.containsBean(beanType, name)
                ? applicationContext.getBean(beanType, name)
                : fallbackProducer.produceBeanInstance(name, beanType);
          return new SimpleContainedBean<>(beanType.cast(instance));
       }

       @Override
       public void stop() {
          // Does nothing
       }
    }

    /**
     * Simple ContainedBean/ManagedBean wrapper used by TestBeanContainer.
     */
    public static class SimpleContainedBean<B> implements ContainedBean<B> {
       private final B instance;

       public SimpleContainedBean(B instance) {
          this.instance = instance;
       }

       @Override
       public Class<B> getBeanClass() {
          return (Class<B>) instance.getClass();
       }

       @Override
       public B getBeanInstance() {
          return instance;
       }
    }
}

Thanks for your attention