DATAES-799 - Polishing.

This commit is contained in:
Peter-Josef Meisch 2020-04-29 20:30:09 +02:00
parent 9b620b31bd
commit 15f51c5151
17 changed files with 106 additions and 97 deletions

View File

@ -79,8 +79,7 @@ public class ElasticsearchExceptionTranslator implements PersistenceExceptionTra
if (exception instanceof ElasticsearchStatusException) {
ElasticsearchStatusException statusException = (ElasticsearchStatusException) exception;
return statusException.status() == RestStatus.CONFLICT
&& statusException.getMessage() != null
return statusException.status() == RestStatus.CONFLICT && statusException.getMessage() != null
&& statusException.getMessage().contains("type=version_conflict_engine_exception")
&& statusException.getMessage().contains("version conflict, required seqNo");
}

View File

@ -272,7 +272,8 @@ class EntityOperations {
*
* @return SeqNoPrimaryTerm, may be {@literal null}
*/
@Nullable SeqNoPrimaryTerm getSeqNoPrimaryTerm();
@Nullable
SeqNoPrimaryTerm getSeqNoPrimaryTerm();
}
/**

View File

@ -709,7 +709,8 @@ class RequestFactory {
return false;
}
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext().getRequiredPersistentEntity(entityClass);
ElasticsearchPersistentEntity<?> entity = elasticsearchConverter.getMappingContext()
.getRequiredPersistentEntity(entityClass);
return entity.hasSeqNoPrimaryTermProperty();
}

View File

@ -15,8 +15,17 @@
*/
package org.springframework.data.elasticsearch.core.convert;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
@ -297,7 +306,8 @@ public class MappingElasticsearchConverter
if (value instanceof List) {
target.add(readValue(value, property, property.getTypeInformation().getActualType()));
} else if (value instanceof Map) {
target.add(readMapValue((Map<String, Object>) value, property, property.getTypeInformation().getActualType()));
target
.add(readMapValue((Map<String, Object>) value, property, property.getTypeInformation().getActualType()));
} else {
target.add(readEntity(computeGenericValueTypeForRead(property, value), (Map<String, Object>) value));
}

View File

@ -182,8 +182,8 @@ public interface Document extends Map<String, Object> {
* {@link #hasSeqNo()} prior to calling this method.
*
* @return the seq_no associated with this {@link Document}.
* @throws IllegalStateException if the underlying implementation supports seq_no's but no seq_no was yet
* associated with the document.
* @throws IllegalStateException if the underlying implementation supports seq_no's but no seq_no was yet associated
* with the document.
*/
default long getSeqNo() {
throw new UnsupportedOperationException();
@ -214,8 +214,8 @@ public interface Document extends Map<String, Object> {
* {@link #hasPrimaryTerm()} prior to calling this method.
*
* @return the primary_term associated with this {@link Document}.
* @throws IllegalStateException if the underlying implementation supports primary_term's but no primary_term was
* yet associated with the document.
* @throws IllegalStateException if the underlying implementation supports primary_term's but no primary_term was yet
* associated with the document.
*/
default long getPrimaryTerm() {
throw new UnsupportedOperationException();

View File

@ -77,8 +77,8 @@ public class DocumentAdapters {
}
if (source.isSourceEmpty()) {
return fromDocumentFields(source, source.getId(), source.getVersion(),
source.getSeqNo(), source.getPrimaryTerm());
return fromDocumentFields(source, source.getId(), source.getVersion(), source.getSeqNo(),
source.getPrimaryTerm());
}
Document document = Document.from(source.getSourceAsMap());
@ -108,8 +108,8 @@ public class DocumentAdapters {
}
if (source.isSourceEmpty()) {
return fromDocumentFields(source, source.getId(), source.getVersion(),
source.getSeqNo(), source.getPrimaryTerm());
return fromDocumentFields(source, source.getId(), source.getVersion(), source.getSeqNo(),
source.getPrimaryTerm());
}
Document document = Document.from(source.getSource());
@ -157,8 +157,7 @@ public class DocumentAdapters {
if (sourceRef == null || sourceRef.length() == 0) {
return new SearchDocumentAdapter(source.getScore(), source.getSortValues(), source.getFields(), highlightFields,
fromDocumentFields(source, source.getId(), source.getVersion(),
source.getSeqNo(), source.getPrimaryTerm()));
fromDocumentFields(source, source.getId(), source.getVersion(), source.getSeqNo(), source.getPrimaryTerm()));
}
Document document = Document.from(source.getSourceAsMap());
@ -180,8 +179,8 @@ public class DocumentAdapters {
* @param documentFields the {@link DocumentField}s backing the {@link Document}.
* @return the adapted {@link Document}.
*/
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String id, long version,
long seqNo, long primaryTerm) {
public static Document fromDocumentFields(Iterable<DocumentField> documentFields, String id, long version, long seqNo,
long primaryTerm) {
if (documentFields instanceof Collection) {
return new DocumentFieldAdapter((Collection<DocumentField>) documentFields, id, version, seqNo, primaryTerm);
@ -204,8 +203,8 @@ public class DocumentAdapters {
private final long seqNo;
private final long primaryTerm;
DocumentFieldAdapter(Collection<DocumentField> documentFields, String id, long version,
long seqNo, long primaryTerm) {
DocumentFieldAdapter(Collection<DocumentField> documentFields, String id, long version, long seqNo,
long primaryTerm) {
this.documentFields = documentFields;
this.id = id;
this.version = version;

View File

@ -60,7 +60,8 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
String settingPath();
@Nullable VersionType getVersionType();
@Nullable
VersionType getVersionType();
boolean isCreateIndexAndMapping();
@ -109,8 +110,8 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
boolean hasSeqNoPrimaryTermProperty();
/**
* Returns the {@link SeqNoPrimaryTerm} property of the {@link ElasticsearchPersistentEntity}. Can be
* {@literal null} in case no such property is available on the entity.
* Returns the {@link SeqNoPrimaryTerm} property of the {@link ElasticsearchPersistentEntity}. Can be {@literal null}
* in case no such property is available on the entity.
*
* @return the {@link SeqNoPrimaryTerm} {@link ElasticsearchPersistentProperty} of the {@link PersistentEntity} or
* {@literal null} if not defined.
@ -131,8 +132,8 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
if (property != null) {
return property;
} else {
throw new IllegalStateException(String.format("Required SeqNoPrimaryTerm property not found for %s!",
this.getType()));
throw new IllegalStateException(
String.format("Required SeqNoPrimaryTerm property not found for %s!", this.getType()));
}
}
}

View File

@ -43,8 +43,8 @@ public interface ElasticsearchPersistentProperty extends PersistentProperty<Elas
* Returns whether the current property is a <em>potential</em> score property of the owning
* {@link ElasticsearchPersistentEntity}. This method is mainly used by {@link ElasticsearchPersistentEntity}
* implementation to discover score property candidates on {@link ElasticsearchPersistentEntity} creation you should
* rather call {@link ElasticsearchPersistentEntity#getScoreProperty()} to determine whether the
* current property is the score property of that {@link ElasticsearchPersistentEntity} under consideration.
* rather call {@link ElasticsearchPersistentEntity#getScoreProperty()} to determine whether the current property is
* the score property of that {@link ElasticsearchPersistentEntity} under consideration.
*
* @return
* @since 3.1

View File

@ -18,21 +18,25 @@ package org.springframework.data.elasticsearch.core.query;
import java.util.Objects;
/**
* <p>A container for seq_no and primary_term values. When an entity class contains a field of this type,
* it will be automatically filled with SeqNoPrimaryTerm instance on read operations (like get or search),
* and also, when the SeqNoPrimaryTerm is not {@literal null} and filled with seq_no and primary_term,
* they will be sent to Elasticsearch when indexing such an entity.
* <p>
* A container for seq_no and primary_term values. When an entity class contains a field of this type, it will be
* automatically filled with SeqNoPrimaryTerm instance on read operations (like get or search), and also, when the
* SeqNoPrimaryTerm is not {@literal null} and filled with seq_no and primary_term, they will be sent to Elasticsearch
* when indexing such an entity.
* </p>
* <p>This allows to implement optimistic locking pattern for full-update scenario, when an entity is first
* read from Elasticsearch and then gets reindexed with new _content.
* Index operations will throw an {@link org.springframework.dao.OptimisticLockingFailureException} if the
* seq_no + primary_term pair already has different values for the given document. See Elasticsearch documentation
* for more information: https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html
* <p>
* This allows to implement optimistic locking pattern for full-update scenario, when an entity is first read from
* Elasticsearch and then gets reindexed with new _content. Index operations will throw an
* {@link org.springframework.dao.OptimisticLockingFailureException} if the seq_no + primary_term pair already has
* different values for the given document. See Elasticsearch documentation for more information:
* https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html
* </p>
* <p>A property of this type is implicitly @{@link org.springframework.data.annotation.Transient} and never gets included
* <p>
* A property of this type is implicitly @{@link org.springframework.data.annotation.Transient} and never gets included
* into a mapping at Elasticsearch side.
* </p>
* <p>A SeqNoPrimaryTerm instance cannot contain an invalid or unassigned seq_no or primary_term.
* <p>
* A SeqNoPrimaryTerm instance cannot contain an invalid or unassigned seq_no or primary_term.
* </p>
*
* @author Roman Puchkovskiy
@ -44,8 +48,8 @@ public final class SeqNoPrimaryTerm {
/**
* Creates an instance of SeqNoPrimaryTerm with the given seq_no and primary_term. The passed values are validated:
* sequenceNumber must be non-negative, primaryTerm must be positive. If validation fails,
* an IllegalArgumentException is thrown.
* sequenceNumber must be non-negative, primaryTerm must be positive. If validation fails, an IllegalArgumentException
* is thrown.
*
* @param sequenceNumber seq_no, must not be negative
* @param primaryTerm primary_term, must be positive
@ -73,10 +77,7 @@ public final class SeqNoPrimaryTerm {
@Override
public String toString() {
return "SeqNoPrimaryTerm{" +
"sequenceNumber=" + sequenceNumber +
", primaryTerm=" + primaryTerm +
'}';
return "SeqNoPrimaryTerm{" + "sequenceNumber=" + sequenceNumber + ", primaryTerm=" + primaryTerm + '}';
}
@Override
@ -88,8 +89,7 @@ public final class SeqNoPrimaryTerm {
return false;
}
SeqNoPrimaryTerm that = (SeqNoPrimaryTerm) o;
return sequenceNumber == that.sequenceNumber &&
primaryTerm == that.primaryTerm;
return sequenceNumber == that.sequenceNumber && primaryTerm == that.primaryTerm;
}
@Override

View File

@ -25,8 +25,6 @@ import org.junit.jupiter.api.Test;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.OptimisticLockingFailureException;
import java.util.UUID;
/**
* @author Roman Puchkovskiy
*/
@ -48,8 +46,8 @@ class ElasticsearchExceptionTranslatorTests {
@Test // DATAES-799
void shouldConvertVersionConflictEngineExceptionWithSeqNoConflictToOptimisticLockingFailureException() {
VersionConflictEngineException ex = new VersionConflictEngineException(
new ShardId("index", "uuid", 1), "exception-id",
VersionConflictEngineException ex = new VersionConflictEngineException(new ShardId("index", "uuid", 1),
"exception-id",
"Elasticsearch exception [type=version_conflict_engine_exception, reason=[WPUUsXEB6uuA6j8_A7AB]: version conflict, required seqNo [34], primary term [16]. current document has seqNo [35] and primary term [16]]");
DataAccessException translated = translator.translateExceptionIfPossible(ex);

View File

@ -62,7 +62,16 @@ import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.Score;
import org.springframework.data.elasticsearch.client.reactive.ReactiveElasticsearchClient;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.*;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.IndexQuery;
import org.springframework.data.elasticsearch.core.query.IndexQueryBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.Query;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.elasticsearch.core.query.StringQuery;
import org.springframework.data.elasticsearch.core.query.UpdateQuery;
import org.springframework.data.elasticsearch.junit.junit4.ElasticsearchVersion;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.util.StringUtils;
@ -858,10 +867,8 @@ public class ReactiveElasticsearchTemplateTests {
original.setMessage("It's fine");
OptimisticEntity saved = template.save(original).block();
template.get(saved.getId(), OptimisticEntity.class)
.as(StepVerifier::create)
.assertNext(this::assertThatSeqNoPrimaryTermIsFilled)
.verifyComplete();
template.get(saved.getId(), OptimisticEntity.class).as(StepVerifier::create)
.assertNext(this::assertThatSeqNoPrimaryTermIsFilled).verifyComplete();
}
private void assertThatSeqNoPrimaryTermIsFilled(OptimisticEntity retrieved) {
@ -878,10 +885,10 @@ public class ReactiveElasticsearchTemplateTests {
original.setMessage("It's fine");
OptimisticEntity saved = template.save(original).block();
template.multiGet(multiGetQueryForOne(saved.getId()), OptimisticEntity.class, template.getIndexCoordinatesFor(OptimisticEntity.class))
.as(StepVerifier::create)
.assertNext(this::assertThatSeqNoPrimaryTermIsFilled)
.verifyComplete();
template
.multiGet(multiGetQueryForOne(saved.getId()), OptimisticEntity.class,
template.getIndexCoordinatesFor(OptimisticEntity.class))
.as(StepVerifier::create).assertNext(this::assertThatSeqNoPrimaryTermIsFilled).verifyComplete();
}
private Query multiGetQueryForOne(String id) {
@ -895,17 +902,15 @@ public class ReactiveElasticsearchTemplateTests {
OptimisticEntity saved = template.save(original).block();
restTemplate.refresh(OptimisticEntity.class);
template.search(searchQueryForOne(saved.getId()), OptimisticEntity.class, template.getIndexCoordinatesFor(OptimisticEntity.class))
.map(SearchHit::getContent)
.as(StepVerifier::create)
.assertNext(this::assertThatSeqNoPrimaryTermIsFilled)
template
.search(searchQueryForOne(saved.getId()), OptimisticEntity.class,
template.getIndexCoordinatesFor(OptimisticEntity.class))
.map(SearchHit::getContent).as(StepVerifier::create).assertNext(this::assertThatSeqNoPrimaryTermIsFilled)
.verifyComplete();
}
private Query searchQueryForOne(String id) {
return new NativeSearchQueryBuilder()
.withFilter(new IdsQueryBuilder().addIds(id))
.build();
return new NativeSearchQueryBuilder().withFilter(new IdsQueryBuilder().addIds(id)).build();
}
@Test // DATAES-799
@ -921,10 +926,7 @@ public class ReactiveElasticsearchTemplateTests {
template.save(forEdit1).block();
forEdit2.setMessage("It'll be great");
template.save(forEdit2)
.as(StepVerifier::create)
.expectError(OptimisticLockingFailureException.class)
.verify();
template.save(forEdit2).as(StepVerifier::create).expectError(OptimisticLockingFailureException.class).verify();
}
@Test // DATAES-799
@ -940,10 +942,7 @@ public class ReactiveElasticsearchTemplateTests {
template.save(forEdit1).block();
forEdit2.setMessage("It'll be great");
template.save(forEdit2)
.as(StepVerifier::create)
.expectError(OptimisticLockingFailureException.class)
.verify();
template.save(forEdit2).as(StepVerifier::create).expectError(OptimisticLockingFailureException.class).verify();
}
@Test // DATAES-799
@ -955,10 +954,7 @@ public class ReactiveElasticsearchTemplateTests {
OptimisticAndVersionedEntity forEdit = template.get(saved.getId(), OptimisticAndVersionedEntity.class).block();
forEdit.setMessage("It'll be ok");
template.save(forEdit)
.as(StepVerifier::create)
.expectNextCount(1)
.verifyComplete();
template.save(forEdit).as(StepVerifier::create).expectNextCount(1).verifyComplete();
}
@Data

View File

@ -233,8 +233,8 @@ class RequestFactoryTests {
when(client.prepareSearch(any())).thenReturn(new SearchRequestBuilder(client, SearchAction.INSTANCE));
Query query = new NativeSearchQueryBuilder().build();
SearchRequestBuilder builder = requestFactory.searchRequestBuilder(client, query,
EntityWithSeqNoPrimaryTerm.class, IndexCoordinates.of("seqNoPrimaryTerm"));
SearchRequestBuilder builder = requestFactory.searchRequestBuilder(client, query, EntityWithSeqNoPrimaryTerm.class,
IndexCoordinates.of("seqNoPrimaryTerm"));
assertThat(builder.request().source().seqNoAndPrimaryTerm()).isTrue();
}

View File

@ -926,7 +926,8 @@ public class MappingElasticsearchConverterUnitTests {
}
@Data
@org.springframework.data.elasticsearch.annotations.Document(indexName = "test-index-entity-with-seq-no-primary-term-mapper")
@org.springframework.data.elasticsearch.annotations.Document(
indexName = "test-index-entity-with-seq-no-primary-term-mapper")
static class EntityWithSeqNoPrimaryTerm {
@Nullable private SeqNoPrimaryTerm seqNoPrimaryTerm;

View File

@ -99,7 +99,8 @@ public class SimpleElasticsearchPersistentEntityTests {
@Test // DATAES-799
void shouldReportThatThereIsNoSeqNoPrimaryTermPropertyWhenThereIsNoSuchProperty() {
TypeInformation<EntityWithoutSeqNoPrimaryTerm> typeInformation = ClassTypeInformation.from(EntityWithoutSeqNoPrimaryTerm.class);
TypeInformation<EntityWithoutSeqNoPrimaryTerm> typeInformation = ClassTypeInformation
.from(EntityWithoutSeqNoPrimaryTerm.class);
SimpleElasticsearchPersistentEntity<EntityWithoutSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation);
@ -108,7 +109,8 @@ public class SimpleElasticsearchPersistentEntityTests {
@Test // DATAES-799
void shouldReportThatThereIsSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = ClassTypeInformation.from(EntityWithSeqNoPrimaryTerm.class);
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = ClassTypeInformation
.from(EntityWithSeqNoPrimaryTerm.class);
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation);
@ -119,7 +121,8 @@ public class SimpleElasticsearchPersistentEntityTests {
@Test // DATAES-799
void shouldReturnSeqNoPrimaryTermPropertyWhenThereIsSuchProperty() {
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = ClassTypeInformation.from(EntityWithSeqNoPrimaryTerm.class);
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = ClassTypeInformation
.from(EntityWithSeqNoPrimaryTerm.class);
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation);
entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm"));
@ -134,7 +137,8 @@ public class SimpleElasticsearchPersistentEntityTests {
@Test // DATAES-799
void shouldNotAllowMoreThanOneSeqNoPrimaryTermProperties() {
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = ClassTypeInformation.from(EntityWithSeqNoPrimaryTerm.class);
TypeInformation<EntityWithSeqNoPrimaryTerm> typeInformation = ClassTypeInformation
.from(EntityWithSeqNoPrimaryTerm.class);
SimpleElasticsearchPersistentEntity<EntityWithSeqNoPrimaryTerm> entity = new SimpleElasticsearchPersistentEntity<>(
typeInformation);
entity.addPersistentProperty(createProperty(entity, "seqNoPrimaryTerm"));
@ -202,8 +206,7 @@ public class SimpleElasticsearchPersistentEntityTests {
@Nullable @Field(name = "renamed-field") private String renamedField;
}
private static class EntityWithoutSeqNoPrimaryTerm {
}
private static class EntityWithoutSeqNoPrimaryTerm {}
private static class EntityWithSeqNoPrimaryTerm {
private SeqNoPrimaryTerm seqNoPrimaryTerm;