Search code examples
springlucenehibernate-searchhibernate-search-6

Index HashMap using keys as fields names. HibernateSearch


I want to index a map<String, Integer> by using the map keys as field names. I have found that by default I can index only keys or values (BuiltinContainerExtractors.MAP_KEY/MAP_VALUES), so I am trying to implement my own binder/bridge. That is my code:

public class SomeEntity {

    @Transient
    @PropertyBinding(binder = @PropertyBinderRef(type = ConceptDistancePropertyBinder.class))
    public Map<String, Integer> getRelated() {
        return ...;
    }
}

public class ConceptDistancePropertyBinder implements PropertyBinder {
    @Override
    public void bind(PropertyBindingContext context) {
        context.dependencies().useRootOnly();
        IndexSchemaElement schemaElement = context.indexSchemaElement();
        IndexSchemaObjectField conceptDistanceField = schemaElement.objectField("conceptDistance");
        conceptDistanceField.fieldTemplate(
                "conceptDistanceTemplate",
                fieldTypeFactory -> fieldTypeFactory.asString().analyzer("default")
        );
        final ConceptDistancePropertyBridge bridge = new ConceptDistancePropertyBridge(conceptDistanceField.toReference());
        context.bridge(Map.class, bridge);
    }
}


public class ConceptDistancePropertyBridge implements PropertyBridge<Map> {

    private final IndexObjectFieldReference conceptDistanceFieldReference;

    public ConceptDistancePropertyBridge(IndexObjectFieldReference conceptDistanceFieldReference) {
        this.conceptDistanceFieldReference = conceptDistanceFieldReference;
    }

    @Override
    public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
        Map<String, Integer> relatedDistanceWithOtherConcepts = (Map<String, Integer>) bridgedElement;
        DocumentElement indexedUserMetadata = target.addObject(conceptDistanceFieldReference);
        relatedDistanceWithOtherConcepts
                .forEach((field, value) -> indexedUserMetadata.addValue(field, field));
    }
}

And I've got an exception:

    Hibernate ORM mapping: 
    type 'com.odysseusinc.prometheus.concept.entity.ConceptEntity': 
        failures: 
          - HSEARCH800007: Unable to resolve path '.related' to a persisted attribute in Hibernate ORM metadata. If this path points to a transient attribute, use @IndexingDependency(derivedFrom = ...) to specify which persisted attributes it is derived from. See the reference documentation for more information.

context.dependencies().useRootOnly() does not help. I also tried to use context.dependencies().use("somefield") and but got another exception

    Hibernate ORM mapping: 
    type 'com.odysseusinc.prometheus.concept.entity.ConceptEntity': 
        path '.related': 
            failures: 
              - HSEARCH700078: No readable property named 'filedName' on type 'java.lang.Integer'

I extracted all necessary code and put it the GitHub https://github.com/YaroslavTir/map-index that really straightforward, and the exception appear after start Application.main


Solution

  • As the error message is telling you:

    If this path points to a transient attribute, use @IndexingDependency(derivedFrom = ...) to specify which persisted attributes it is derived from. See the reference documentation for more information.

    In particular, see this section.

    In short, add the annotation to getRelated() so that Hibernate Search knows which properties you derived your map from:

    public class SomeEntity {
    
        @Transient
        @PropertyBinding(binder = @PropertyBinderRef(type = ConceptDistancePropertyBinder.class))
        @IndexingDependency(
            derivedFrom = {
                @ObjectPath(@PropertyValue(propertyName = "someProperty1")),
                @ObjectPath({@PropertyValue(propertyName = "someProperty2"), @PropertyValue(propertyName = "someNestedProperty")})
            },
            extraction = @ContainerExtraction(extract = ContainerExtract.NO)
        )
        public Map<String, Integer> getRelated() {
            return ...;
        }
    }
    

    Note that in your case, you have to specify explicitly in @IndexingDependency that the entire map is "derived", not just the values (which is what Hibernate Search would target by default, as you noticed). That's why you need extraction = @ContainerExtraction(extract = ContainerExtract.NO): this essentially tells Hibernate Search "this metadata applies to the whole map, not just to the values".