-
-
Save yrodiere/9ff8fe8a8c7f59c1a051b36db20fbd4d to your computer and use it in GitHub Desktop.
Thoughts around the "document builder" (which isn't a builder) in Hibernate Search 6.0
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Requirements addressed by this code: | |
* - Support multiple types of input entities (POJO, JsonObject, ...), each having its own metadata | |
=> Abstracted by the "ValueProcessor" interface | |
* - Support multiple types of output documents (Lucene, ES, JMS/JGroups byte arrays, ...) | |
=> Abstracted by the "DocumentBuilder" interface | |
* - Do not tie types of input entities to certain types of output documents (the two concepts are independent) | |
=> The process of walking through an entity is handled separately from the process of producing a document | |
* - Do as little checks as possible at runtime when inspecting the input source | |
=> The value processors are built from the metadata when bootstrapping | |
*/ | |
/* | |
* TODO: rename ValueProcessor to something less generic. | |
* Maybe ValueDisassembler? Something else? | |
*/ | |
/* | |
* "Facade" exposed to the rest of Hibernate Search (WorkPlanner and so on) | |
* Could also be named "EntityWalker" or "DocumentBuilder", but I find those names | |
* less fitting, considering what we expect from this class. | |
*/ | |
public interface EntityDocumentConverter<E, D> { | |
D convert(E entity); | |
} | |
// Internal, engine impl | |
class EntityDocumentConverterImpl<E, D> implements EntityDocumentConverter<E, D> { | |
private ValueProcessor<? super E> rootProcessor; | |
private Supplier<? extends DocumentBuilder<? extends D>> builderSupplier; | |
public D convert(E entity) { | |
DocumentBuilder<? extends D> builder = builderSupplier.get(); | |
rootProcessor.process(entity, builder); | |
return builder.build(); | |
} | |
} | |
// ------------------------------------------------------- | |
// Document building SPI - 1 impl for each output type (Lucene, ES, ...) | |
public interface DocumentContext { | |
void addFieldValue(FieldId<T> fieldId, T value); | |
DocumentContext nest(SingleValueNestingId nestingId); | |
DocumentContext nest(IndexedValueNestingId nestingId, int index); | |
} | |
public interface DocumentBuilder<D> extends DocumentContext { | |
D build(); | |
} | |
// Instances are passed to SPI, both when building metadata and when building documents | |
public interface FieldId<T> { | |
// Just equals/hashCode here: these IDs are just used as keys | |
} | |
// Instances are passed to SPI, both when building metadata and when building documents | |
public interface SingleValueNestingId { | |
// Just equals/hashCode here: these IDs are just used as keys | |
} | |
// Instances are passed to SPI | |
public interface IndexedValueNestingId { | |
// Just equals/hashCode here: these IDs are just used as keys | |
} | |
// ------------------------------------------------------- | |
// Entity processing SPI - 1 impl for each input entity type (POJO, com.google.gson.JsonObject, ...) | |
public interface ValueProcessor<V> { | |
void process(V value, DocumentContext documentContext); | |
} | |
// ------------------------------------------------------- | |
// Bridge SPI | |
// SPI | |
// TODO declare the document value type somehow? | |
public interface SimpleBridge<A extends Annotation> { | |
void initialize(A annotation) default {}; | |
Object convert(Object value); | |
} | |
// SPI | |
public interface ComplexBridge<A extends Annotation> { | |
void initialize(MetadataHandle handle, A annotation); | |
/* | |
* We probably won't want to expose the full context here, but rather a specific interface, | |
* which would enable us to expose only the features we actually want to expose | |
* (no nesting, etc.), and maybe only expose them later. | |
* Anyway, I left the full context for now. | |
*/ | |
void set(DocumentContext documentContext, Object value); | |
} | |
// Instances are passed to the ComplexBridge SPI | |
public interface MetadataHandle { | |
<T> FieldId<T> registerField(String relativeName, Class<T> valueType); // Or maybe some kind of more complex builder, but that's the spirit. | |
/* | |
* Those methods could allow complex bridges to define nesting in a very fine-grained fashion. | |
* We can wait and only implement that kind of thing in 6.1, though. | |
*/ | |
SingleValueNestingId registerSingleValueNesting(String prefix); // Or maybe some kind of more complex builder, but that's the spirit. | |
IndexedValueNestingId registerIndexedValueNesting(String prefix); // Or maybe some kind of more complex builder, but that's the spirit. | |
} | |
// ------------------------------------------------------- | |
// Internal implementation (may be exposed to service providers as a util) | |
final class CompositeProcessor<V> implements ValueProcessor<V> { | |
private final Collection<ValueProcessor<? super V>> processors; | |
public void process(V value, DocumentContext documentContext) { | |
for (ValueProcessor<? super V> processor : processors) { | |
processor.process(value, documentContext); | |
} | |
} | |
} | |
// Internal implementation | |
final class SimpleBridgeValueProcessor<V> implements ValueProcessor<V> { | |
private final SimpleBridge<?> bridge; | |
private final FieldId<Object> fieldId; | |
public void process(V value, DocumentContext documentContext) { | |
documentContext.addFieldValue(fieldId, bridge.convert(value)); | |
} | |
} | |
// Internal implementation | |
final class ComplexBridgeValueProcessor<V> implements ValueProcessor<V> { | |
private final ComplexBridge<?> bridge; | |
public void process(V value, DocumentContext documentContext) { | |
bridge.set(documentContext, value); | |
} | |
} | |
// ------------------------------------------------------- | |
// Implementations of the entity processing SPI for POJO (samples) | |
// H: property holder type, P: property value type | |
class JavaPropertyValueProcessor<H, P> implements ValueProcessor<H> { | |
private final XProperty<H, P> property; | |
// The processor below makes use of the field bridge (see ComplexBridgeProcessor for instance) | |
private final ValueProcessor<P> propertyValueProcessor; | |
public void process(H holderValue, DocumentContext documentContext) { | |
if ( holderValue != null ) { | |
propertyValueProcessor.process(property.get(holderValue), documentContext); | |
} | |
} | |
} | |
// H: property holder type, P: property value type | |
class GsonPropertyValueProcessor implements ValueProcessor<JsonObject> { | |
private final String propertyName; | |
// The processor below makes use of the field bridge (see ComplexBridgeProcessor for instance) | |
private final ValueProcessor<JsonElement> propertyValueProcessor; | |
public void process(JsonObject holderValue, DocumentContext documentContext) { | |
if (holderValue.has(propertyName)) { | |
propertyValueProcessor.process(holderValue.get(propertyName), documentContext); | |
} | |
} | |
} | |
// Use for single-value @IndexedEmbeddeds | |
class SingleNestedValueProcessor<V> implements ValueProcessor<V> { | |
// The processor below is composed of all processors for nested fields | |
private final CompositeValueProcessor<V> fieldsProcessor; | |
private final SingleValueNestingId nestingId; | |
public void process(V value, DocumentContext documentContext) { | |
if ( value != null ) { | |
DocumentContext nestedContext = documentContext.nest(nestingId); | |
fieldsProcessor.process(value, nestedContext); | |
} | |
} | |
} | |
// Use for multi-value, Iterable-implementing @IndexedEmbeddeds | |
class IterableNestedValueProcessor<I extends Iterable<V>, V> implements ValueProcessor<I> { | |
// The processor below is composed of all processors for nested fields | |
private final CompositeValueProcessor<V> fieldsProcessor; | |
private final IndexedValueNestingId nestingId; | |
public void process(I iterable, DocumentContext documentContext) { | |
if ( iterable != null ) { | |
int index = 0; | |
for ( V value : iterable ) { | |
DocumentContext nestedContext = documentContext.nest(nestingId, index); | |
fieldsProcessor.process(value, nestedContext); | |
++index; | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment