PropertyBinder to create additional Lucene field

Trying to migrate to Hibernate Search 6.

The idea is to have enum class indexed to be able to filter out by enum name and also to create additional Integer field in Lucene to have sort ability.

The goal is to get 2 fields in Lucene index:

  1. importance (Text, searchable, containing enum item name (“LOW”, “HIGH”…)
  2. importance_numeric (Numeric, sortablem, containing score from enum item)?

Which type of bridge shoud I use?
What type of @*Field to put on Importance property inside Article?


@AllArgsConstructor @Getter 
enum Importance {
	  FIRST(3),
	    TOP(2),
	   HIGH(1),
	 NORMAL(0),
	   LOW(-1),
	BOTTOM(-2),
	  LAST(-3);
	private final int score;
}

Enum is used inside Article class:

@Entity @Indexed 
class Article {

	 // ...

	@Enumerated(EnumType.STRING)
	@Field(
		index = Index.YES,
		analyze = Analyze.NO,
		store = Store.NO,
		bridge = @FieldBridge(impl = ImportanceBridge.class)
	)
	@SortableField
	private Importance importance = Importance.NORMAL;

	 // ...

}

Bridge from Hibernate Search 5 was pretty simple:

class ImportanceBridge implements MetadataProvidingFieldBridge {

	@Override
	public void configureFieldMetadata(
		String name,
		FieldMetadataBuilder builder
	) {
		builder.field(name + "_numeric", FieldType.INTEGER).sortable(true);
	}

	@Override
	public void set(
		String name, Object object, Document document,
		LuceneOptions luceneOptions
	) {
		if (!(object instanceof Importance)) {
			return;
		}
		Importance i = (Importance) object;
		int score = i.getScore();
		luceneOptions.addNumericFieldToDocument(
			name + "_numeric", Integer.valueOf(score), document
		);
		document.add(new NumericDocValuesField(name + "_numeric", score));
	}
}

Tried to create this as PropertyBinder but I got lost.

class ImportanceBridge implements PropertyBinder {

	private static final String PROPERTY_NAME = "importance";
	private static final String SUFFIX        = "_numeric";

	@Override
	public void bind(PropertyBindingContext context) {

		context.dependencies().use(PROPERTY_NAME);

		IndexSchemaObjectField importanceNumericField =
			context.indexSchemaElement().objectField(PROPERTY_NAME + SUFFIX);

	//	-- don't know what to do with this :(
	//	IndexFieldType<Integer> importanceNumericFieldType =
	//		context.typeFactory().asInteger().toIndexFieldType();

		context.bridge(Importance.class, new ImportancePropertyBridge(
			importanceNumericField.toReference()
		));
	}

	private static class ImportancePropertyBridge
			implements PropertyBridge<Importance> {

		private final IndexObjectFieldReference importanceFieldReference;

		private ImportancePropertyBridge(
			IndexObjectFieldReference importanceFieldReference
		) {
			this.importanceFieldReference = importanceFieldReference;
		}

		@Override
		public void write(
			DocumentElement target, Importance bridgedElement,
			PropertyBridgeWriteContext context
		) {
			Integer intValue = Integer.valueOf(bridgedElement.getScore());
			target.addObject( this.importanceFieldReference );

			// ????? what next?

        }

    }

}

Is that the way to do it?

@Entity @Indexed 
class Article {

	 // ...

	@GenericField(
		projectable = Projectable.NO,
		searchable = Searchable.YES,
		sortable = Sortable.NO
	)
	@GenericField(
		projectable = Projectable.NO,
		searchable = Searchable.NO,
		sortable = Sortable.YES,
		name="importance_numeric",
		valueBridge = @ValueBridgeRef(type = ImportanceBridge.class)
	)
	@Enumerated(EnumType.STRING)
	private Importance importance = Importance.NORMAL;

	 // ...

}

public class ImportanceBridge implements ValueBridge<Importance, Integer> {

	@Override
	public Integer toIndexedValue(
		Importance value,
		ValueBridgeToIndexedValueContext context
	) {
		if (value == null) {
			log.warn("{} Importance should not be NULL", LOG_PREFIX);
			throw new IllegalStateException("Importance should not be NULL");
		}
		return Integer.valueOf(value.getScore());
	}

}

Yes that’s one solution.

If you want to use only one annotation, you can do this instead:

// NOTE: this is a binder, not a bridge.
class ImportanceBinder implements PropertyBinder {

	@Override
	public void bind(PropertyBindingContext context) {

		// This bridge is applied to properties of enum type, and that type is immutable.
		// Thus we don't need to declare precise dependencies.
		context.dependencies().useRootOnly();

		context.bridge(Importance.class, new ImportancePropertyBridge(
			context.indexSchemaElement()
					.field(context.bridgedElement().name(), f -> f.asString())
					.toReference(),
			context.indexSchemaElement()
					.field(context.bridgedElement().name() + "_numeric", f -> f.asInteger()
							.searchable(Searchable.NO).sortable(Sortable.YES))
					.toReference()
		));
	}

	private static class ImportancePropertyBridge
			implements PropertyBridge<Importance> {

		private final IndexFieldReference<String> stringField;
		private final IndexFieldReference<Integer> integerField;

		private ImportancePropertyBridge(
			IndexFieldReference<String> stringField,
			IndexFieldReference<Integer> integerField
		) {
			this.stringField = stringField;
			this.integerField = integerField;
		}

		@Override
		public void write(
			DocumentElement target, Importance bridgedElement,
			PropertyBridgeWriteContext context
		) {
			target.addValue( stringField, bridgedElement.name() );
			target.addValue( integerField, bridgedElement.getScore() );
		}
    }

}
@Entity @Indexed 
class Article {

	 // ...

	@PropertyBinding(binder = @PropertyBinderRef(type = ImportanceBinder.class))
	@Enumerated(EnumType.STRING)
	private Importance importance = Importance.NORMAL;

	 // ...

}

See also PropertyBinder, Declaring fields, Declaring field types.

1 Like

Thank you very much!