Runtime polymophism with TypeBinder bridge

Hi All,

We are trying to use a type binder for the following entity model. We want to be able to search EntityToSearch using the fullName in the ConcreteClassA (extends AbstractEntity) using a TypeBinder. The main problem is that the @OneToOne association between ConcreteClassA and EntityToSearch is inherited from the abstract super class.

public class EntityToSearch { 

   @OneToOne
   @IndexedEmbedded(includeEmbeddedObjectId = true)
   private AbstractEntity parentEntity;

   //other fields
}

// No field to search here
@Inheritance(strategy = InheritanceType.JOINED)
@TypeBinding(binder = @TypeBinderRef(type = FullNameBinder.class))
public abstract class AbstractEntity {

  @OneToOne(mappedBy = "parentEntity")
   private EntityToSearch entityToSearch ;
  //other fields
}

@PrimaryKeyJoinColumn(name = "concreteClassA _id")
ConcreteClassA extends AbstractEntity {
  
  //Field to search 
  @KeywordField
  private String fullName;
}

public class FullNameBinder implements TypeBinder {
    @Override
    public void bind(final TypeBindingContext context) {
       IndexFieldReference<String> fullName= context.indexSchemaElement().field("fullName", IndexFieldTypeFactory::asString).toReference();
       context.bridge(ConcreteClassA.class, new MyBridge(fullName));
    }

private static class MyBridge implements TypeBridge<AbstractEntity> {

		private final IndexFieldReference<String> fullName;

		private MyBridge(IndexFieldReference<String> fullNameField) {
			this.fullName= fullNameField;
		}

		@Override
		public void write(DocumentElement target, AbstractEntity abstractEntity , TypeBridgeWriteContext context) {
			if (abstractEntity instanceof ConcreteClassA ) {
				target.addValue(this.fullName, ((ConcreteClassA ) abstractEntity ).getFullName());
			}
		}

	}
}

Our main problem for this bridge to work is the bind function, we first tried to use the dependencies().useRootOnly(), but as indicated in the documentation that means declaring no dependency at all. We tried now to use context.dependencies().fromOtherEntity(ConcreteClassA .class, "?") or context.dependencies().use() but we don’t understand how and witch one to use in our case.

Hello,

This is basically the same problem as the one exposed in this post.

In short, the API is not designed to allow what you want to do, but there are workarounds.

I opened [HSEARCH-4488] - Hibernate JIRA to provide ways to address this use case without hacks.
Also, related: [HSEARCH-438] - Hibernate JIRA

This answer might work for you:

Adapting to your code:


// No field to search here
@Inheritance(strategy = InheritanceType.JOINED)
@TypeBinding(binder = @TypeBinderRef(type = FullNameBinder.class))
public abstract class AbstractEntity {

  @OneToOne(mappedBy = "parentEntity")
   private EntityToSearch entityToSearch ;
  //other fields

  // Hack, see https://discourse.hibernate.org/t/runtime-polymophism-with-typebinder-bridge/6057
  @Transient
  public abstract AbstractEntity getSelf();
}

@PrimaryKeyJoinColumn(name = "concreteClassA _id")
ConcreteClassA extends AbstractEntity {
  
  //Field to search 
  // @KeywordField // NOTE: Commented out because you don't need this if you already index fullName in your bridge.
  private String fullName;

  // Hack, see https://discourse.hibernate.org/t/runtime-polymophism-with-typebinder-bridge/6057
  @Override
  public ConcreteClassA getSelf() {
    return this;
  }
}

public class FullNameBinder implements TypeBinder {
    @Override
    public void bind(final TypeBindingContext context) {
       IndexFieldReference<String> fullName= context.indexSchemaElement().field("fullName", IndexFieldTypeFactory::asString).toReference();
        // Hack, see https://discourse.hibernate.org/t/runtime-polymophism-with-typebinder-bridge/6057
        context.dependencies().fromOtherEntity( ConcreteClassA.class, "self" )
            .use("fullName");
       context.bridge(AbstractEntity.class, new MyBridge(fullName));
    }

private static class MyBridge implements TypeBridge<AbstractEntity> {

		private final IndexFieldReference<String> fullName;

		private MyBridge(IndexFieldReference<String> fullNameField) {
			this.fullName= fullNameField;
		}

		@Override
		public void write(DocumentElement target, AbstractEntity abstractEntity , TypeBridgeWriteContext context) {
			if (abstractEntity instanceof ConcreteClassA ) {
				target.addValue(this.fullName, ((ConcreteClassA ) abstractEntity ).getFullName());
			}
		}

	}
}

Hello, thanks for the ticket creation and for your fast answer, unforunately this solution is not working for our case. Now the hibernate mapping seems good but if the fullName value is updated, elastic index is not updated. Also, is it possible to change the context.dependencies().fromOtherEntity( ConcreteClassA.class, “self” )
.use(“fullName”); depending on the context.bridgeElement, In our case we got some specific field to ConcreteClassA, but also some to another class ConcreteClassB.

I guess it would look like :

    @Override
    public void bind(final TypeBindingContext context) {

    if (context.bridgedElement instance of ConcreteClassA) {
       IndexFieldReference<String> fullName= context.indexSchemaElement().field("fullName", IndexFieldTypeFactory::asString).toReference();
        // Hack, see https://discourse.hibernate.org/t/runtime-polymophism-with-typebinder-bridge/6057
        context.dependencies().fromOtherEntity( ConcreteClassA.class, "self" )
            .use("fullName");
       context.bridge(AbstractEntity.class, new MyBridge(fullName, null));
    } else if (context.bridgedElement instance of ConcreteClassB) {
       IndexFieldReference<String> firstName= context.indexSchemaElement().field("firstName", IndexFieldTypeFactory::asString).toReference();
       context.dependencies().fromOtherEntity( ConcreteClassB.class, "self" )
            .use("firstName");
       context.bridge(AbstractEntity.class, new MyBridge(null, firstName));
    }
}

I just tested this solution, and as far as I can tell, it works: HSEARCH-4491 Test hack involving fromOtherEntity and getSelf to support runtime polymorphism in bridges by yrodiere · Pull Request #2906 · hibernate/hibernate-search · GitHub

Maybe check that you implemented exactly the solution I provided. If you can’t find a difference, try to come up with a reproducer (you can start from this template), and I’ll have a look.

Yes, it’s possible, and actually that’s exactly what I did in the original solution (the one provided in the other topic I linked to).

No. When bind is called, you don’t have a bridgeElement to test. bind is called on bootstrap, but you want runtime polymorphism.

It will look more like this:

    @Override
    public void bind(final TypeBindingContext context) {
       IndexFieldReference<String> fullName= context.indexSchemaElement().field("fullName", IndexFieldTypeFactory::asString).toReference();
       IndexFieldReference<String> firstName= context.indexSchemaElement().field("firstName", IndexFieldTypeFactory::asString).toReference();
        // Hack, see https://discourse.hibernate.org/t/runtime-polymophism-with-typebinder-bridge/6057
        context.dependencies().fromOtherEntity( ConcreteClassA.class, "self" )
            .use("fullName");
        context.dependencies().fromOtherEntity( ConcreteClassB.class, "self" )
            .use("firstName");
        context.bridge(AbstractEntity.class, new MyBridge(fullName, firstName));
    }
}

Thank you for responding so quickly, I did not pay attention enough and did not implement exactly your response, everything if working as expected, thanks for your support.

1 Like