Migrating @FieldBridge to v6.1 (Map case)

Hi there!

I’m still trying to migrate @FieldBridge annotations, but in this case refers to a Map<String, Integer> property.

I’ve defined the property this way:

@PropertyBinding(binder = @PropertyBinderRef(type = MapValueBinder.class))
@GenericField(searchable = Searchable.YES, projectable = Projectable.YES, name = "permission")
protected Map<String, Integer> permissions = new HashMap<>();

Using this custom PropertyBinder:

public class MapValueBinder implements PropertyBinder {
	private static final Logger log = LoggerFactory.getLogger(MapValueBinder.class);

	@Override
	public void bind(PropertyBindingContext context) {
		context.dependencies().useRootOnly();
		IndexSchemaElement schemaElement = context.indexSchemaElement();
		IndexSchemaObjectField userMetadataField = schemaElement.objectField("permissionBinder");
		userMetadataField.fieldTemplate("permissionTemplate", IndexFieldTypeFactory::asString);

		context.bridge(Map.class, new MapValueBridge(userMetadataField.toReference()));
	}

	@SuppressWarnings("rawtypes")
	private static class MapValueBridge implements PropertyBridge<Map> {
		private final IndexObjectFieldReference userMetadataFieldReference;

		private MapValueBridge(IndexObjectFieldReference userMetadataFieldReference) {
			this.userMetadataFieldReference = userMetadataFieldReference;
		}

		@Override
		public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
			@SuppressWarnings("unchecked")
			Map<String, Integer> permissions = (Map<String, Integer>) bridgedElement;
			DocumentElement indexedUserMetadata = target.addObject(userMetadataFieldReference);

			for (Map.Entry<String, Integer> elto : permissions.entrySet()) {
				if (elto.getValue() > 10) {
					indexedUserMetadata.addValue("myFieldName", elto.getKey());
				}
			}
		}
	}
}

Looking at Lucene indexe, what I get is:

  • permission (the map entry value)
  • permissionBinder.myFieldName (my custom value)

But I want several “permission” fields with a given value, not the one from the map entry. How can I do that?

Thanks in advence!

Hi,

If you don’t want to put the fields on the permissionBinder object field, but on its parent instead… just remove the object field, and put the fields on the parent?

 public class MapValueBinder implements PropertyBinder {
 	private static final Logger log = LoggerFactory.getLogger(MapValueBinder.class);
 
 	@Override
 	public void bind(PropertyBindingContext context) {
 		context.dependencies().useRootOnly();
 		IndexSchemaElement schemaElement = context.indexSchemaElement();
- 		IndexSchemaObjectField userMetadataField = schemaElement.objectField("permissionBinder");
- 		userMetadataField.fieldTemplate("permissionTemplate", IndexFieldTypeFactory::asString);
+ 		schemaElement.fieldTemplate("permissionTemplate", IndexFieldTypeFactory::asString);
 
-		context.bridge(Map.class, new MapValueBridge(userMetadataField.toReference()));
+		context.bridge(Map.class, new MapValueBridge());
 	}
 
 	@SuppressWarnings("rawtypes")
 	private static class MapValueBridge implements PropertyBridge<Map> {
 		private final IndexObjectFieldReference userMetadataFieldReference;
 
 		private MapValueBridge(IndexObjectFieldReference userMetadataFieldReference) {
 			this.userMetadataFieldReference = userMetadataFieldReference;
 		}
 
 		@Override
 		public void write(DocumentElement target, Map bridgedElement, PropertyBridgeWriteContext context) {
 			@SuppressWarnings("unchecked")
 			Map<String, Integer> permissions = (Map<String, Integer>) bridgedElement;
-			DocumentElement indexedUserMetadata = target.addObject(userMetadataFieldReference);
 
 			for (Map.Entry<String, Integer> elto : permissions.entrySet()) {
 				if (elto.getValue() > 10) {
- 					indexedUserMetadata.addValue("myFieldName", elto.getKey());
+ 					target.addValue("myFieldName", elto.getKey());
 				}
 			}
 		}
 	}
 }

Thanks, it worked but when there is more tha one element in the map, an exception is thrown:

HSEARCH600074: Multiple values assigned to field 'myFieldName': this field is single-valued. Declare the field as multi-valued in order to allow this.

This migration is being much more complex than expected :sweat:

The solution is in the error message:

Declare the field as multi-valued in order to allow this.

See Hibernate Search 6.1.5.Final: Reference Documentation

Ok, I’ve solved this way:

schemaElement.fieldTemplate("permissionTemplate", f -> f.asString()
	.searchable(Searchable.YES).projectable(Projectable.YES))
	.multiValued();

But I’ve another doubt: in Lucene document I have now both fields: permission and myFieldName. And I’m not sure how I can avoid adding the permission field. If I change myFielName to permission an error is thrown, so the only way I see is defining the field this way:

@PropertyBinding(binder = @PropertyBinderRef(type = MapValueBinder.class))
@GenericField(searchable = Searchable.NO, projectable = Projectable.NO)
protected Map<String, Integer> permissions = new HashMap<>();

And seems to work, but not sure if this has other undeseable implications. Even this also seems to work and not sure which is the best approach:

@PropertyBinding(binder = @PropertyBinderRef(type = MapValueBinder.class))
protected Map<String, Integer> permissions = new HashMap<>();

I don’t even know what you’re trying to do, so it’s hard to give you advice :slight_smile:

Why do use use both @PropertyBinding and @GenericField in the first place?

You do realize that the only effect of your @GenericField annotation is to index the map values in a permissions field? I’m not sure what’s the point of that.

Well, I’m still trying to understand this new version of Hibernate Search because has many, many changes. In v5.6 I was using:

@Field(index = Index.YES, analyze = Analyze.NO, store = Store.YES)
@FieldBridge(impl = MapFieldBridge.class)
protected Map<String, Integer> permissions = new HashMap<>();

I implemented a custom MapFieldBridge which added other values to the Lucene document, and worked pretty fine. It was much easier than now. I was trying to reproduce the same behaviour with the new version. I know right now is more search engine independent, but things are now much complex.

In short, I wanted to add values from the information stored in the Map (according to certain logic) using a give name, in this case “myFieldName” (as many as elements in the Map which matches a certain criteria).

Considering you’re only creating fields using your custom bridge here, In Hibernate Search 6 you most likely only need @PropertyBinding, not @GenericField.

It might not be obvious in Hibernate Search 5, but the @FieldBridge annotation does nothing on its own: it just configures the @Field annotation further.

To understand Hibernate Search 6 better, I suggest you have a look at the documentation.
The table in Bridge basics might help.

If necessary, for more general topics, there is an extensive migration guide here: Hibernate Search 6.0.9.Final: Migration Guide from 5.11

Things were already complex: hibernate Search 5 made assumptions that made things look simpler, but could often result in wrong behavior (missing reindexing, wrong field types) if you didn’t know better, or even limitations (no faceting for bridge-created fields, …).

So no, this “added” complexity is not just about Elasticsearch support.

I’ve already read the Hibernate Search 6.0.9.Final: Migration Guide from 5.11 and helped, but I have some cases which are out of the scope of this guide, or are not very intuitive like @FieldBridge migration.

By the way, is there any project with working code samples? I mean a simple Hibernate Search project where people can take a look and understand better how to use this new version improvements properly. I know the documentation is great but working samples would make it better.

Thanks a lot!

There’s a showcase here, as linked from the getting started guide.

There are also various resources listed on the website, most of which include a link to a GitHub repository.

Otherwise, you can find a few open-source projects relying on Hibernate Search here: Network Dependents · hibernate/hibernate-search · GitHub I’m afraid most applications using Hibernate Search are closed-source, though…

Great, thanks for the links!