2020import org .apache .lucene .analysis .tokenattributes .TypeAttribute ;
2121import org .apache .lucene .document .Field ;
2222import org .apache .lucene .document .FieldType ;
23+ import org .apache .lucene .document .StoredField ;
2324import org .apache .lucene .index .IndexOptions ;
25+ import org .apache .lucene .util .BytesRef ;
2426import org .elasticsearch .ElasticsearchParseException ;
2527import org .elasticsearch .index .IndexVersion ;
2628import org .elasticsearch .index .analysis .AnalyzerScope ;
2729import org .elasticsearch .index .analysis .IndexAnalyzers ;
2830import org .elasticsearch .index .analysis .NamedAnalyzer ;
31+ import org .elasticsearch .index .mapper .CompositeSyntheticFieldLoader ;
2932import org .elasticsearch .index .mapper .DocumentParserContext ;
3033import org .elasticsearch .index .mapper .FieldMapper ;
3134import org .elasticsearch .index .mapper .KeywordFieldMapper ;
3235import org .elasticsearch .index .mapper .MapperBuilderContext ;
33- import org .elasticsearch .index .mapper .SourceFieldMapper ;
36+ import org .elasticsearch .index .mapper .SourceLoader ;
3437import org .elasticsearch .index .mapper .StringStoredFieldFieldLoader ;
38+ import org .elasticsearch .index .mapper .TextFamilyFieldMapper ;
3539import org .elasticsearch .index .mapper .TextFieldMapper ;
3640import org .elasticsearch .index .mapper .TextParams ;
3741import org .elasticsearch .index .mapper .TextSearchInfo ;
6165 * This code is largely a copy of TextFieldMapper which is less than ideal -
6266 * my attempts to subclass TextFieldMapper failed but we can revisit this.
6367 **/
64- public class AnnotatedTextFieldMapper extends FieldMapper {
68+ public class AnnotatedTextFieldMapper extends TextFamilyFieldMapper {
6569
6670 public static final String CONTENT_TYPE = "annotated_text" ;
6771
@@ -84,28 +88,26 @@ public static class Builder extends FieldMapper.Builder {
8488 final Parameter <String > indexOptions = TextParams .textIndexOptions (m -> builder (m ).indexOptions .getValue ());
8589 final Parameter <Boolean > norms = TextParams .norms (true , m -> builder (m ).norms .getValue ());
8690 final Parameter <String > termVectors = TextParams .termVectors (m -> builder (m ).termVectors .getValue ());
91+ private final Parameter <Boolean > store = Parameter .storeParam (m -> builder (m ).store .getValue (), false );
8792
8893 private final Parameter <Map <String , String >> meta = Parameter .metaParam ();
8994
9095 private final IndexVersion indexCreatedVersion ;
9196 private final TextParams .Analyzers analyzers ;
92- private final boolean isSyntheticSourceEnabled ;
93- private final Parameter <Boolean > store ;
97+ private final boolean isWithinMultiField ;
9498
95- public Builder (String name , IndexVersion indexCreatedVersion , IndexAnalyzers indexAnalyzers , boolean isSyntheticSourceEnabled ) {
99+ private boolean isSyntheticSourceEnabled ;
100+
101+ public Builder (String name , IndexVersion indexCreatedVersion , IndexAnalyzers indexAnalyzers , boolean isWithinMultiField ) {
96102 super (name );
97103 this .indexCreatedVersion = indexCreatedVersion ;
104+ this .isWithinMultiField = isWithinMultiField ;
98105 this .analyzers = new TextParams .Analyzers (
99106 indexAnalyzers ,
100107 m -> builder (m ).analyzers .getIndexAnalyzer (),
101108 m -> builder (m ).analyzers .positionIncrementGap .getValue (),
102109 indexCreatedVersion
103110 );
104- this .isSyntheticSourceEnabled = isSyntheticSourceEnabled ;
105- this .store = Parameter .storeParam (
106- m -> builder (m ).store .getValue (),
107- () -> isSyntheticSourceEnabled && multiFieldsBuilder .hasSyntheticSourceCompatibleKeywordField () == false
108- );
109111 }
110112
111113 @ Override
@@ -154,6 +156,7 @@ public AnnotatedTextFieldMapper build(MapperBuilderContext context) {
154156 }
155157 }
156158 BuilderParams builderParams = builderParams (this , context );
159+ this .isSyntheticSourceEnabled = context .isSourceSynthetic ();
157160 return new AnnotatedTextFieldMapper (
158161 leafName (),
159162 fieldType ,
@@ -165,7 +168,7 @@ public AnnotatedTextFieldMapper build(MapperBuilderContext context) {
165168 }
166169
167170 public static final TypeParser PARSER = new TypeParser (
168- (n , c ) -> new Builder (n , c .indexVersionCreated (), c .getIndexAnalyzers (), SourceFieldMapper . isSynthetic ( c . getIndexSettings () ))
171+ (n , c ) -> new Builder (n , c .indexVersionCreated (), c .getIndexAnalyzers (), c . isWithinMultiField ( ))
169172 );
170173
171174 /**
@@ -505,9 +508,9 @@ public String typeName() {
505508 }
506509 }
507510
511+ private final IndexVersion indexCreatedVersion ;
508512 private final FieldType fieldType ;
509513 private final Builder builder ;
510-
511514 private final NamedAnalyzer indexAnalyzer ;
512515
513516 protected AnnotatedTextFieldMapper (
@@ -517,11 +520,26 @@ protected AnnotatedTextFieldMapper(
517520 BuilderParams builderParams ,
518521 Builder builder
519522 ) {
520- super (simpleName , mappedFieldType , builderParams );
523+ super (
524+ simpleName ,
525+ builder .indexCreatedVersion ,
526+ builder .isSyntheticSourceEnabled ,
527+ builder .isWithinMultiField ,
528+ mappedFieldType ,
529+ builderParams
530+ );
531+
521532 assert fieldType .tokenized ();
533+
522534 this .fieldType = freezeAndDeduplicateFieldType (fieldType );
523535 this .builder = builder ;
524536 this .indexAnalyzer = wrapAnalyzer (builder .analyzers .getIndexAnalyzer ());
537+ this .indexCreatedVersion = builder .indexCreatedVersion ;
538+ }
539+
540+ @ Override
541+ public AnnotatedTextFieldType fieldType () {
542+ return (AnnotatedTextFieldType ) super .fieldType ();
525543 }
526544
527545 @ Override
@@ -531,18 +549,32 @@ public Map<String, NamedAnalyzer> indexAnalyzers() {
531549
532550 @ Override
533551 protected void parseCreateField (DocumentParserContext context ) throws IOException {
534- final String value = context .parser ().textOrNull ();
552+ final var value = context .parser ().optimizedTextOrNull ();
535553
536554 if (value == null ) {
537555 return ;
538556 }
539557
540558 if (fieldType .indexOptions () != IndexOptions .NONE || fieldType .stored ()) {
541- Field field = new Field (mappedFieldType .name (), value , fieldType );
559+ Field field = new Field (mappedFieldType .name (), value . string () , fieldType );
542560 context .doc ().add (field );
543561 if (fieldType .omitNorms ()) {
544562 context .addToFieldNames (fieldType ().name ());
545563 }
564+ } else if (needsToSupportSyntheticSource () && fieldType .stored () == false ) {
565+ // if synthetic source needs to be supported, yet the field isn't stored, then we need to rely on something
566+ // else to support synthetic source
567+
568+ // if we can rely on the synthetic source delegate for synthetic source, then return
569+ if (fieldType ().canUseSyntheticSourceDelegateForSyntheticSource (value .string ())) {
570+ return ;
571+ }
572+
573+ // otherwise, we need to store a copy of this value so that synthetic source can load it
574+ var utfBytes = value .bytes ();
575+ var bytesRef = new BytesRef (utfBytes .bytes (), utfBytes .offset (), utfBytes .length ());
576+ final String fieldName = fieldType ().syntheticSourceFallbackFieldName (true );
577+ context .doc ().add (new StoredField (fieldName , bytesRef ));
546578 }
547579 }
548580
@@ -553,8 +585,7 @@ protected String contentType() {
553585
554586 @ Override
555587 public FieldMapper .Builder getMergeBuilder () {
556- return new Builder (leafName (), builder .indexCreatedVersion , builder .analyzers .indexAnalyzers , builder .isSyntheticSourceEnabled )
557- .init (this );
588+ return new Builder (leafName (), builder .indexCreatedVersion , builder .analyzers .indexAnalyzers , isWithinMultiField ).init (this );
558589 }
559590
560591 @ Override
@@ -568,11 +599,31 @@ protected void write(XContentBuilder b, Object value) throws IOException {
568599 });
569600 }
570601
602+ return new SyntheticSourceSupport .Native (() -> syntheticFieldLoader (fullPath (), leafName ()));
603+ }
604+
605+ private SourceLoader .SyntheticFieldLoader syntheticFieldLoader (String fullFieldName , String leafFieldName ) {
606+ // since we don't know whether the delegate field loader can be used for synthetic source until parsing, we
607+ // need to check both this field and the delegate
608+
609+ // first field loader, representing this field
610+ final String fieldName = fieldType ().syntheticSourceFallbackFieldName (isSyntheticSourceEnabled );
611+ final var thisFieldLayer = new CompositeSyntheticFieldLoader .StoredFieldLayer (fieldName ) {
612+ @ Override
613+ protected void writeValue (Object value , XContentBuilder b ) throws IOException {
614+ b .value (value .toString ());
615+ }
616+ };
617+
618+ final CompositeSyntheticFieldLoader fieldLoader = new CompositeSyntheticFieldLoader (leafFieldName , fullFieldName , thisFieldLayer );
619+
620+ // second loader, representing a delegate field, if one exists
571621 var kwd = TextFieldMapper .SyntheticSourceHelper .getKeywordFieldMapperForSyntheticSource (this );
572622 if (kwd != null ) {
573- return new SyntheticSourceSupport .Native (() -> kwd .syntheticFieldLoader (fullPath (), leafName ()));
623+ // merge the two field loaders into one
624+ return fieldLoader .mergedWith (kwd .syntheticFieldLoader (fullPath (), leafName ()));
574625 }
575626
576- return super . syntheticSourceSupport () ;
627+ return fieldLoader ;
577628 }
578629}
0 commit comments