Skip to content

Commit be93ebd

Browse files
authored
Can use @ScriptedFields annotated property as ctor parameter in records and Kotlin data classes.
Original Pull Request #1802 Closes #1488
1 parent 159687e commit be93ebd

File tree

3 files changed

+163
-44
lines changed

3 files changed

+163
-44
lines changed

src/main/java/org/springframework/data/elasticsearch/core/document/DocumentAdapters.java

+51-43
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.Collection;
2424
import java.util.HashMap;
2525
import java.util.LinkedHashMap;
26-
import java.util.LinkedHashSet;
2726
import java.util.List;
2827
import java.util.Map;
2928
import java.util.Objects;
@@ -63,36 +62,38 @@
6362
* @author Matt Gilene
6463
* @since 4.0
6564
*/
66-
public class DocumentAdapters {
65+
public final class DocumentAdapters {
66+
67+
private DocumentAdapters() {}
6768

6869
/**
6970
* Create a {@link Document} from {@link GetResponse}.
7071
* <p>
71-
* Returns a {@link Document} using the source if available.
72+
* Returns a {@link Document} using the getResponse if available.
7273
*
73-
* @param source the source {@link GetResponse}.
74-
* @return the adapted {@link Document}, null if source.isExists() returns false.
74+
* @param getResponse the getResponse {@link GetResponse}.
75+
* @return the adapted {@link Document}, null if getResponse.isExists() returns false.
7576
*/
7677
@Nullable
77-
public static Document from(GetResponse source) {
78+
public static Document from(GetResponse getResponse) {
7879

79-
Assert.notNull(source, "GetResponse must not be null");
80+
Assert.notNull(getResponse, "GetResponse must not be null");
8081

81-
if (!source.isExists()) {
82+
if (!getResponse.isExists()) {
8283
return null;
8384
}
8485

85-
if (source.isSourceEmpty()) {
86-
return fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(),
87-
source.getPrimaryTerm());
86+
if (getResponse.isSourceEmpty()) {
87+
return fromDocumentFields(getResponse, getResponse.getIndex(), getResponse.getId(), getResponse.getVersion(),
88+
getResponse.getSeqNo(), getResponse.getPrimaryTerm());
8889
}
8990

90-
Document document = Document.from(source.getSourceAsMap());
91-
document.setIndex(source.getIndex());
92-
document.setId(source.getId());
93-
document.setVersion(source.getVersion());
94-
document.setSeqNo(source.getSeqNo());
95-
document.setPrimaryTerm(source.getPrimaryTerm());
91+
Document document = Document.from(getResponse.getSourceAsMap());
92+
document.setIndex(getResponse.getIndex());
93+
document.setId(getResponse.getId());
94+
document.setVersion(getResponse.getVersion());
95+
document.setSeqNo(getResponse.getSeqNo());
96+
document.setPrimaryTerm(getResponse.getPrimaryTerm());
9697

9798
return document;
9899
}
@@ -188,9 +189,10 @@ public static SearchDocument from(SearchHit source) {
188189

189190
if (sourceRef == null || sourceRef.length() == 0) {
190191
return new SearchDocumentAdapter(
191-
source.getScore(), source.getSortValues(), source.getFields(), highlightFields, fromDocumentFields(source,
192-
source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()),
193-
innerHits, nestedMetaData, explanation, matchedQueries);
192+
fromDocumentFields(source, source.getIndex(), source.getId(), source.getVersion(), source.getSeqNo(),
193+
source.getPrimaryTerm()),
194+
source.getScore(), source.getSortValues(), source.getFields(), highlightFields, innerHits, nestedMetaData,
195+
explanation, matchedQueries);
194196
}
195197

196198
Document document = Document.from(source.getSourceAsMap());
@@ -203,8 +205,8 @@ public static SearchDocument from(SearchHit source) {
203205
document.setSeqNo(source.getSeqNo());
204206
document.setPrimaryTerm(source.getPrimaryTerm());
205207

206-
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
207-
document, innerHits, nestedMetaData, explanation, matchedQueries);
208+
return new SearchDocumentAdapter(document, source.getScore(), source.getSortValues(), source.getFields(),
209+
highlightFields, innerHits, nestedMetaData, explanation, matchedQueries);
208210
}
209211

210212
@Nullable
@@ -243,6 +245,10 @@ private static List<String> from(@Nullable String[] matchedQueries) {
243245
*
244246
* @param documentFields the {@link DocumentField}s backing the {@link Document}.
245247
* @param index the index where the Document was found
248+
* @param id the document id
249+
* @param version the document version
250+
* @param seqNo the seqNo if the document
251+
* @param primaryTerm the primaryTerm of the document
246252
* @return the adapted {@link Document}.
247253
*/
248254
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String index, String id,
@@ -261,10 +267,13 @@ public static Document fromDocumentFields(Iterable<DocumentField> documentFields
261267
return new DocumentFieldAdapter(fields, index, id, version, seqNo, primaryTerm);
262268
}
263269

264-
// TODO: Performance regarding keys/values/entry-set
270+
/**
271+
* Adapter for a collection of {@link DocumentField}s.
272+
*/
265273
static class DocumentFieldAdapter implements Document {
266274

267275
private final Collection<DocumentField> documentFields;
276+
private final Map<String, DocumentField> documentFieldMap;
268277
private final String index;
269278
private final String id;
270279
private final long version;
@@ -274,6 +283,8 @@ static class DocumentFieldAdapter implements Document {
274283
DocumentFieldAdapter(Collection<DocumentField> documentFields, String index, String id, long version, long seqNo,
275284
long primaryTerm) {
276285
this.documentFields = documentFields;
286+
this.documentFieldMap = new LinkedHashMap<>(documentFields.size());
287+
documentFields.forEach(documentField -> documentFieldMap.put(documentField.getName(), documentField));
277288
this.index = index;
278289
this.id = id;
279290
this.version = version;
@@ -353,14 +364,7 @@ public boolean isEmpty() {
353364

354365
@Override
355366
public boolean containsKey(Object key) {
356-
357-
for (DocumentField documentField : documentFields) {
358-
if (documentField.getName().equals(key)) {
359-
return true;
360-
}
361-
}
362-
363-
return false;
367+
return documentFieldMap.containsKey(key);
364368
}
365369

366370
@Override
@@ -380,11 +384,9 @@ public boolean containsValue(Object value) {
380384
@Override
381385
@Nullable
382386
public Object get(Object key) {
383-
return documentFields.stream() //
384-
.filter(documentField -> documentField.getName().equals(key)) //
385-
.map(DocumentField::getValue).findFirst() //
386-
.orElse(null); //
387387

388+
DocumentField documentField = documentFieldMap.get(key);
389+
return documentField != null ? documentField.getValue() : null;
388390
}
389391

390392
@Override
@@ -409,17 +411,18 @@ public void clear() {
409411

410412
@Override
411413
public Set<String> keySet() {
412-
return documentFields.stream().map(DocumentField::getName).collect(Collectors.toCollection(LinkedHashSet::new));
414+
return documentFieldMap.keySet();
413415
}
414416

415417
@Override
416418
public Collection<Object> values() {
417-
return documentFields.stream().map(DocumentFieldAdapter::getValue).collect(Collectors.toList());
419+
return documentFieldMap.values().stream().map(DocumentFieldAdapter::getValue).collect(Collectors.toList());
418420
}
419421

420422
@Override
421423
public Set<Entry<String, Object>> entrySet() {
422-
return documentFields.stream().collect(Collectors.toMap(DocumentField::getName, DocumentFieldAdapter::getValue))
424+
return documentFieldMap.entrySet().stream()
425+
.collect(Collectors.toMap(Entry::getKey, entry -> DocumentFieldAdapter.getValue(entry.getValue())))
423426
.entrySet();
424427
}
425428

@@ -458,7 +461,6 @@ public String toJson() {
458461
}
459462
}
460463

461-
462464
@Override
463465
public String toString() {
464466
return getClass().getSimpleName() + '@' + this.id + '#' + this.version + ' ' + toJson();
@@ -494,14 +496,14 @@ static class SearchDocumentAdapter implements SearchDocument {
494496
@Nullable private final Explanation explanation;
495497
@Nullable private final List<String> matchedQueries;
496498

497-
SearchDocumentAdapter(float score, Object[] sortValues, Map<String, DocumentField> fields,
498-
Map<String, List<String>> highlightFields, Document delegate, Map<String, SearchDocumentResponse> innerHits,
499+
SearchDocumentAdapter(Document delegate, float score, Object[] sortValues, Map<String, DocumentField> fields,
500+
Map<String, List<String>> highlightFields, Map<String, SearchDocumentResponse> innerHits,
499501
@Nullable NestedMetaData nestedMetaData, @Nullable Explanation explanation,
500502
@Nullable List<String> matchedQueries) {
501503

504+
this.delegate = delegate;
502505
this.score = score;
503506
this.sortValues = sortValues;
504-
this.delegate = delegate;
505507
fields.forEach((name, documentField) -> this.fields.put(name, documentField.getValues()));
506508
this.highlightFields.putAll(highlightFields);
507509
this.innerHits.putAll(innerHits);
@@ -646,7 +648,13 @@ public boolean containsValue(Object value) {
646648

647649
@Override
648650
public Object get(Object key) {
649-
return delegate.get(key);
651+
652+
if (delegate.containsKey(key)) {
653+
return delegate.get(key);
654+
}
655+
656+
// fallback to fields
657+
return fields.get(key);
650658
}
651659

652660
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2019-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.elasticsearch.core.mapping;
17+
18+
import org.springframework.data.mapping.model.CamelCaseSplittingFieldNamingStrategy;
19+
20+
/**
21+
* @author Peter-Josef Meisch
22+
* @since 4.3
23+
*/
24+
public class KebabCaseFieldNamingStrategy extends CamelCaseSplittingFieldNamingStrategy {
25+
public KebabCaseFieldNamingStrategy() {
26+
super("-");
27+
}
28+
}

src/test/java/org/springframework/data/elasticsearch/core/ElasticsearchTemplateTests.java

+84-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.assertj.core.api.Assertions.*;
2020
import static org.elasticsearch.index.query.QueryBuilders.*;
2121
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
22+
import static org.springframework.data.elasticsearch.annotations.FieldType.Integer;
2223
import static org.springframework.data.elasticsearch.core.document.Document.*;
2324
import static org.springframework.data.elasticsearch.utils.IdGenerator.*;
2425
import static org.springframework.data.elasticsearch.utils.IndexBuilder.*;
@@ -3585,6 +3586,31 @@ void shouldWorkWithImmutableClasses() {
35853586
assertThat(retrieved).isEqualTo(saved);
35863587
}
35873588

3589+
@Test // #1488
3590+
@DisplayName("should set scripted fields on immutable objects")
3591+
void shouldSetScriptedFieldsOnImmutableObjects() {
3592+
3593+
ImmutableWithScriptedEntity entity = new ImmutableWithScriptedEntity("42", 42, null);
3594+
operations.save(entity);
3595+
3596+
Map<String, Object> params = new HashMap<>();
3597+
params.put("factor", 2);
3598+
NativeSearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchAllQuery())
3599+
.withSourceFilter(new FetchSourceFilter(new String[] { "*" }, new String[] {}))
3600+
.withScriptField(
3601+
new ScriptField("scriptedRate", new Script(ScriptType.INLINE, "expression", "doc['rate'] * factor", params)))
3602+
.build();
3603+
3604+
SearchHits<ImmutableWithScriptedEntity> searchHits = operations.search(searchQuery,
3605+
ImmutableWithScriptedEntity.class);
3606+
3607+
assertThat(searchHits.getTotalHits()).isEqualTo(1);
3608+
ImmutableWithScriptedEntity foundEntity = searchHits.getSearchHit(0).getContent();
3609+
assertThat(foundEntity.getId()).isEqualTo("42");
3610+
assertThat(foundEntity.getRate()).isEqualTo(42);
3611+
assertThat(foundEntity.getScriptedRate()).isEqualTo(84.0);
3612+
}
3613+
35883614
// region entities
35893615
@Document(indexName = INDEX_NAME_SAMPLE_ENTITY)
35903616
@Setting(shards = 1, replicas = 0, refreshInterval = "-1")
@@ -4366,7 +4392,7 @@ public void setText(@Nullable String text) {
43664392

43674393
@Document(indexName = "immutable-class")
43684394
private static final class ImmutableEntity {
4369-
@Id private final String id;
4395+
@Id @Nullable private final String id;
43704396
@Field(type = FieldType.Text) private final String text;
43714397
@Nullable private final SeqNoPrimaryTerm seqNoPrimaryTerm;
43724398

@@ -4376,6 +4402,7 @@ public ImmutableEntity(@Nullable String id, String text, @Nullable SeqNoPrimaryT
43764402
this.seqNoPrimaryTerm = seqNoPrimaryTerm;
43774403
}
43784404

4405+
@Nullable
43794406
public String getId() {
43804407
return id;
43814408
}
@@ -4419,5 +4446,61 @@ public String toString() {
44194446
+ seqNoPrimaryTerm + '}';
44204447
}
44214448
}
4449+
4450+
@Document(indexName = "immutable-scripted")
4451+
public static final class ImmutableWithScriptedEntity {
4452+
@Id private final String id;
4453+
@Field(type = Integer) @Nullable private final int rate;
4454+
@Nullable @ScriptedField private final Double scriptedRate;
4455+
4456+
public ImmutableWithScriptedEntity(String id, int rate, @Nullable java.lang.Double scriptedRate) {
4457+
this.id = id;
4458+
this.rate = rate;
4459+
this.scriptedRate = scriptedRate;
4460+
}
4461+
4462+
public String getId() {
4463+
return id;
4464+
}
4465+
4466+
public int getRate() {
4467+
return rate;
4468+
}
4469+
4470+
@Nullable
4471+
public Double getScriptedRate() {
4472+
return scriptedRate;
4473+
}
4474+
4475+
@Override
4476+
public boolean equals(Object o) {
4477+
if (this == o)
4478+
return true;
4479+
if (o == null || getClass() != o.getClass())
4480+
return false;
4481+
4482+
ImmutableWithScriptedEntity that = (ImmutableWithScriptedEntity) o;
4483+
4484+
if (rate != that.rate)
4485+
return false;
4486+
if (!id.equals(that.id))
4487+
return false;
4488+
return scriptedRate != null ? scriptedRate.equals(that.scriptedRate) : that.scriptedRate == null;
4489+
}
4490+
4491+
@Override
4492+
public int hashCode() {
4493+
int result = id.hashCode();
4494+
result = 31 * result + rate;
4495+
result = 31 * result + (scriptedRate != null ? scriptedRate.hashCode() : 0);
4496+
return result;
4497+
}
4498+
4499+
@Override
4500+
public String toString() {
4501+
return "ImmutableWithScriptedEntity{" + "id='" + id + '\'' + ", rate=" + rate + ", scriptedRate=" + scriptedRate
4502+
+ '}';
4503+
}
4504+
}
44224505
// endregion
44234506
}

0 commit comments

Comments
 (0)