TypeBridge for transient object

I’m trying to implement a TypeBridge for a transient object. The object implements Map and has arbitrary keys.

I feel like I’m missing some important piece of context, and am coming across errors that don’t make sense to me.

@TypeBinding(binder = TypeBinderRef(type = ProductAttributesTypeBinder::class))
class ProductAttributes(val product: Product) : MutableMap<String, Any?> by product._attributes {
    val changes = mutableMapOf<String, Any?>();
    protected val types: Map<String, BaseAttributeType<Any?>> by lazy {
        product.family.attributes.fold(mutableMapOf<String, BaseAttributeType<Any?>>(), { acc, attr ->
            acc[attr.code!!] = attr.type!! as BaseAttributeType<Any?>
            acc
        })
    }

    override fun put(key: String, value: Any?): Any? {
        val v = product._attributes[key]
        val type = types[key] ?: throw InvalidAttributeException("`$key` is not a valid attribute on this product")
        type.validate(value)
        product._attributes[key] = value
        this.changes[key] = value
        return v
    }

    override fun get(key: String): Any? {
        return product._attributes[key]
    }
}
@Entity
@Indexed
@TypeDefs(TypeDef(name = "jsonb", typeClass = JsonBinaryType::class))
open class Product protected constructor() {
    @Id
    @GeneratedValue
    var id: Long? = null

// ...

    @Transient
//    @PropertyBinding(binder = PropertyBinderRef(type = ProductAttributesBinder::class))
    @IndexedEmbedded(targetType = ProductAttributes::class)
    val attributes = ProductAttributes(this)

// ...
} 
class ProductAttributesTypeBinder : TypeBinder {
    override fun bind(context: TypeBindingContext) {
//        context.dependencies().use(PojoModelPath.builder().property( "product" ).property("_attributes").toValuePath())
        context.dependencies().use("product._attributes")
//        context.dependencies().useRootOnly()
        val attributesField = context.indexSchemaElement().objectField("attributes")
        attributesField.fieldTemplate("attributes_string") { f -> f.asString().analyzer("default")}
        context.setBridge(ProductAttributesTypeBridge(attributesField.toReference()))

    }
}
class ProductAttributesTypeBridge(val fieldReference: IndexObjectFieldReference) : TypeBridge {
    override fun write(target: DocumentElement, bridgedElement: Any, context: TypeBridgeWriteContext) {
        val attributes = bridgedElement as ProductAttributes
        val indexedAttributes: DocumentElement = target.addObject(fieldReference)
        attributes.entries.forEach {
            indexedAttributes.addValue(it.key, it.value)
        }
    }
}

No matter how I try to implement this, I get errors like the below. Any suggestions on how to start with indexing the attributes property? I eventually want to all the attribute types to control the way the attribute values are indexed.

Caused by: org.hibernate.search.util.common.SearchException: HSEARCH000520: Hibernate Search encountered failures during bootstrap. Failures:

    Hibernate ORM mapping: 
        type 'com.tradetested.pim.domain.catalog.Product': 
            path '.attributes<map-value>': 
                failures: 
                  - HSEARCH800010: Unable to find a readable property 'product' on type 'java.lang.Object'.
	at org.hibernate.search.engine.reporting.spi.RootFailureCollector.checkNoFailure(RootFailureCollector.java:50)
	at org.hibernate.search.engine.common.impl.SearchIntegrationBuilderImpl.prepareBuild(SearchIntegrationBuilderImpl.java:259)
	at org.hibernate.search.mapper.orm.bootstrap.impl.HibernateOrmIntegrationBooterImpl.doBootFirstPhase(HibernateOrmIntegrationBooterImpl.java:250)
	at org.hibernate.search.mapper.orm.bootstrap.impl.HibernateOrmIntegrationBooterImpl.preBoot(HibernateOrmIntegrationBooterImpl.java:128)
	at io.quarkus.hibernate.search.elasticsearch.runtime.HibernateSearchElasticsearchRecorder$HibernateSearchIntegrationListener.onMetadataInitialized(HibernateSearchElasticsearchRecorder.java:85)
	at io.quarkus.hibernate.orm.runtime.integration.HibernateOrmIntegrations.onMetadataInitialized(HibernateOrmIntegrations.java:27)
	at io.quarkus.hibernate.orm.runtime.boot.FastBootMetadataBuilder.build(FastBootMetadataBuilder.java:306)
	at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.createMetadata(PersistenceUnitsHolder.java:101)
	at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.constructMetadataAdvance(PersistenceUnitsHolder.java:73)
	at io.quarkus.hibernate.orm.runtime.PersistenceUnitsHolder.initializeJpa(PersistenceUnitsHolder.java:40)
	at io.quarkus.hibernate.orm.runtime.HibernateOrmRecorder$4.created(HibernateOrmRecorder.java:88)
	at io.quarkus.arc.runtime.ArcRecorder.initBeanContainer(ArcRecorder.java:106)
	at io.quarkus.deployment.steps.ArcProcessor$generateResources-1120260799.deploy_0(ArcProcessor$generateResources-1120260799.zig:520)
	at io.quarkus.deployment.steps.ArcProcessor$generateResources-1120260799.deploy(ArcProcessor$generateResources-1120260799.zig:36)
	at io.quarkus.runner.ApplicationImpl.<clinit>(ApplicationImpl.zig:160)
	... 66 more

What you’re seeing here is the failure report, mentioning all failures that Hibernate Search encountered during startup. It’s meant to be succinct, which is why it doesn’t include the stack trace. But you can find the stack trace higher in your logs.

And if you look up this stack trace, you will probably find that the problem lies in this line:

The path you’re passing to use(...) is incorrect, for two reasons:

  1. The prefix product. doesn’t make sense in this context. You’re applying the type bridge to class ProductAttributes, and that class doesn’t expose a property named product.
  2. Even if it made sense, there is no property named _attributes in Product.

Anyway, in your case, if ProductAttributes is transient, there’s no reason to declare dependencies to begin with. You can just use this to let Hibernate Search know that you don’t care about changes inside the product attributes:

context.dependencies().useRootOnly();

If it’s not clear what dependencies() is for, I suggest you read this explanation. Any suggestions to clarify the documentation or the naming of methods are welcome.