diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java
index f1d01a69..f45f33ed 100644
--- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java
+++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolution.java
@@ -170,7 +170,8 @@ protected MigrationService createMigrationService() {
getRestClient(),
ContentType.parse(getConfig().getDefaultContentType()),
getConfig().getEncoding(),
- getConfig().getValidateOnMigrate(),
- getConfig().getBaselineVersion());
+ getConfig().isValidateOnMigrate(),
+ getConfig().getBaselineVersion(),
+ getConfig().isOutOfOrder());
}
}
diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java
index db04ac8e..10b242c2 100644
--- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java
+++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfig.java
@@ -106,6 +106,14 @@ public class ElasticsearchEvolutionConfig {
*/
private String baselineVersion = "1.0";
+ /**
+ * Allows migrations to be run “out of order”.
+ *
+ * If you already have versions 1.0 and 3.0 applied, and now a version 2.0 is found,
+ * it will be applied too instead of being rejected.
+ */
+ private boolean outOfOrder = false;
+
/**
* Loads this configuration into a new ElasticsearchEvolution instance.
*
@@ -147,11 +155,13 @@ public ElasticsearchEvolutionConfig validate() throws IllegalStateException, Nul
}
requireNotBlank(historyIndex, "historyIndex must not be empty");
requireCondition(historyMaxQuerySize, size -> size > 0, "historyMaxQuerySize value '%s' must be greater than 0", historyMaxQuerySize);
+ final MigrationVersion baseline;
try {
- MigrationVersion.fromVersion(baselineVersion);
+ baseline = MigrationVersion.fromVersion(baselineVersion);
} catch (RuntimeException e) {
throw new IllegalArgumentException("baselineVersion is invalid", e);
}
+ requireCondition(baseline, version -> version.isAtLeast("1"), "baselineVersion '%s' must be at least 1", baseline);
}
return this;
}
@@ -273,7 +283,15 @@ public ElasticsearchEvolutionConfig setHistoryMaxQuerySize(int historyMaxQuerySi
return this;
}
+ /**
+ * @deprecated use {@link #isValidateOnMigrate()} instead
+ */
+ @Deprecated
public boolean getValidateOnMigrate() {
+ return isValidateOnMigrate();
+ }
+
+ public boolean isValidateOnMigrate() {
return validateOnMigrate;
}
@@ -291,6 +309,15 @@ public ElasticsearchEvolutionConfig setBaselineVersion(String baselineVersion) {
return this;
}
+ public boolean isOutOfOrder() {
+ return outOfOrder;
+ }
+
+ public ElasticsearchEvolutionConfig setOutOfOrder(boolean outOfOrder) {
+ this.outOfOrder = outOfOrder;
+ return this;
+ }
+
@Override
public String toString() {
return "ElasticsearchEvolutionConfig{" +
@@ -309,6 +336,7 @@ public String toString() {
", historyMaxQuerySize=" + historyMaxQuerySize +
", validateOnMigrate=" + validateOnMigrate +
", baselineVersion='" + baselineVersion + '\'' +
+ ", outOfOrder='" + outOfOrder + '\'' +
'}';
}
}
diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImpl.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImpl.java
index 7cf4c554..b12b2615 100644
--- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImpl.java
+++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImpl.java
@@ -3,6 +3,7 @@
import com.senacor.elasticsearch.evolution.core.api.MigrationException;
import com.senacor.elasticsearch.evolution.core.api.migration.HistoryRepository;
import com.senacor.elasticsearch.evolution.core.api.migration.MigrationService;
+import com.senacor.elasticsearch.evolution.core.internal.model.MigrationVersion;
import com.senacor.elasticsearch.evolution.core.internal.model.dbhistory.MigrationScriptProtocol;
import com.senacor.elasticsearch.evolution.core.internal.model.migration.ParsedMigrationScript;
import com.senacor.elasticsearch.evolution.core.internal.utils.RandomUtils;
@@ -38,8 +39,10 @@ public class MigrationServiceImpl implements MigrationService {
private final ContentType defaultContentType;
private final Charset encoding;
private final boolean validateOnMigrate;
+ private final boolean outOfOrder;
private final String baselineVersion;
+
public MigrationServiceImpl(HistoryRepository historyRepository,
int waitUntilUnlockedMinTimeInMillis,
int waitUntilUnlockedMaxTimeInMillis,
@@ -47,7 +50,8 @@ public MigrationServiceImpl(HistoryRepository historyRepository,
ContentType defaultContentType,
Charset encoding,
boolean validateOnMigrate,
- String baselineVersion) {
+ String baselineVersion,
+ boolean outOfOrder) {
this.historyRepository = requireNonNull(historyRepository, "historyRepository must not be null");
this.restClient = requireNonNull(restClient, "restClient must not be null");
this.defaultContentType = requireNonNull(defaultContentType);
@@ -59,6 +63,7 @@ public MigrationServiceImpl(HistoryRepository historyRepository,
waitUntilUnlockedMinTimeInMillis, waitUntilUnlockedMaxTimeInMillis);
this.waitUntilUnlockedMaxTimeInMillis = waitUntilUnlockedMaxTimeInMillis;
this.baselineVersion = baselineVersion;
+ this.outOfOrder = outOfOrder;
}
@Override
@@ -180,53 +185,75 @@ List getPendingScriptsToBeExecuted(Collection orderedScripts = new ArrayList<>(migrationScripts.stream()
+ final TreeMap scriptsInFilesystemMap = migrationScripts.stream()
.filter(script -> script.getFileNameInfo().getVersion().isAtLeast(baselineVersion))
.collect(Collectors.toMap(
script -> script.getFileNameInfo().getVersion(),
script -> script,
(oldValue, newValue) -> newValue,
- TreeMap::new))
- .values());
+ TreeMap::new));
List history = new ArrayList<>(historyRepository.findAll());
- List res = new ArrayList<>(orderedScripts);
- for (int i = 0; i < history.size(); i++) {
- // do some checks
- MigrationScriptProtocol protocol = history.get(i);
- if (orderedScripts.size() <= i) {
- logger.warn(String.format("there are less migration scripts than already executed history entries! " +
- "You should never delete migration scripts you have already executed. " +
- "Or maybe you have to cleanup the Elasticsearch-Evolution history index manually! " +
- "history version at position %s is %s", i, protocol.getVersion()));
- break;
- }
- ParsedMigrationScript parsedMigrationScript = orderedScripts.get(i);
- if (!protocol.getVersion().equals(parsedMigrationScript.getFileNameInfo().getVersion())) {
- throw new MigrationException(String.format(
- "The logged execution in the Elasticsearch-Evolution history index at position %s " +
- "is version %s and in the same position in the given migration scripts is version %s! " +
- "Out of order execution is not supported. Or maybe you have added new migration scripts " +
- "in between or have to cleanup the Elasticsearch-Evolution history index manually",
- i, protocol.getVersion(), parsedMigrationScript.getFileNameInfo().getVersion()));
- }
- // failed scripts can be edited and retried, but successfully executed scripts may not be modified afterwards
- if (validateOnMigrate && protocol.isSuccess() && protocol.getChecksum() != parsedMigrationScript.getChecksum()) {
- throw new MigrationException(String.format(
- "The logged execution for the migration script at position %s (%s) " +
- "has a different checksum from the given migration script! " +
- "Modifying already-executed scripts is not supported.",
- i, protocol.getScriptName()));
+ List res = new ArrayList<>(scriptsInFilesystemMap.values());
+ if (outOfOrder) {
+ for (MigrationScriptProtocol protocol : history) {
+ final ParsedMigrationScript parsedMigrationScript = scriptsInFilesystemMap.get(protocol.getVersion());
+ if (null == parsedMigrationScript) {
+ logger.warn("there are less migration scripts than already executed history entries! " +
+ "You should never delete migration scripts you have already executed. " +
+ "Or maybe you have to cleanup the Elasticsearch-Evolution history index manually! " +
+ "Already executed history version {} is not present in migration files", protocol.getVersion());
+ } else {
+ validateOnMigrateIfEnabled(protocol, parsedMigrationScript);
+
+ if (protocol.isSuccess()) {
+ res.remove(parsedMigrationScript);
+ }
+ }
}
+ } else {
+ List orderedScripts = new ArrayList<>(scriptsInFilesystemMap.values());
+ for (int i = 0; i < history.size(); i++) {
+ // do some checks
+ MigrationScriptProtocol protocol = history.get(i);
+ if (orderedScripts.size() <= i) {
+ logger.warn("there are less migration scripts than already executed history entries! " +
+ "You should never delete migration scripts you have already executed. " +
+ "Or maybe you have to cleanup the Elasticsearch-Evolution history index manually! " +
+ "history version at position {} is {}", i, protocol.getVersion());
+ break;
+ }
+ ParsedMigrationScript parsedMigrationScript = orderedScripts.get(i);
+ if (!protocol.getVersion().equals(parsedMigrationScript.getFileNameInfo().getVersion())) {
+ throw new MigrationException(String.format(
+ "The logged execution in the Elasticsearch-Evolution history index at position %s " +
+ "is version %s and in the same position in the given migration scripts is version %s! " +
+ "Out of order execution is not supported. Or maybe you have added new migration scripts " +
+ "in between or have to cleanup the Elasticsearch-Evolution history index manually",
+ i, protocol.getVersion(), parsedMigrationScript.getFileNameInfo().getVersion()));
+ }
+ validateOnMigrateIfEnabled(protocol, parsedMigrationScript);
- if (protocol.isSuccess()) {
- res.remove(parsedMigrationScript);
+ if (protocol.isSuccess()) {
+ res.remove(parsedMigrationScript);
+ }
}
}
return res;
}
+ private void validateOnMigrateIfEnabled(MigrationScriptProtocol protocol, ParsedMigrationScript parsedMigrationScript) {
+ // failed scripts can be edited and retried, but successfully executed scripts may not be modified afterward
+ if (validateOnMigrate && protocol.isSuccess() && protocol.getChecksum() != parsedMigrationScript.getChecksum()) {
+ throw new MigrationException(String.format(
+ "The logged execution for the migration script version %s (%s) " +
+ "has a different checksum from the given migration script! " +
+ "Modifying already-executed scripts is not supported.",
+ protocol.getVersion(), protocol.getScriptName()));
+ }
+ }
+
/**
* wait until the elasticsearch-evolution history index is unlocked
*/
diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/input/MigrationScriptReaderImpl.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/input/MigrationScriptReaderImpl.java
index 1e09899f..acbf6e8e 100644
--- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/input/MigrationScriptReaderImpl.java
+++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/migration/input/MigrationScriptReaderImpl.java
@@ -3,8 +3,9 @@
import com.senacor.elasticsearch.evolution.core.api.MigrationException;
import com.senacor.elasticsearch.evolution.core.api.migration.MigrationScriptReader;
import com.senacor.elasticsearch.evolution.core.internal.model.migration.RawMigrationScript;
+import java.util.regex.Pattern;
import org.reflections.Reflections;
-import org.reflections.scanners.ResourcesScanner;
+import org.reflections.scanners.Scanners;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
@@ -147,10 +148,13 @@ private Stream readScriptsFromClassPath(String location) {
resources = emptySet();
} else {
Reflections reflections = new Reflections(new ConfigurationBuilder()
- .setScanners(new ResourcesScanner())
+ .setScanners(Scanners.Resources)
.filterInputsBy(new FilterBuilder().includePackage(locationWithoutPrefixAsPackageNotation))
.setUrls(urls));
- resources = reflections.getResources(this::isValidFilename);
+ resources = reflections.getResources(Pattern.compile(esMigrationPrefix + ".*"))
+ .stream()
+ .filter(path -> isValidFilename(Paths.get(path).getFileName().toString()))
+ .collect(Collectors.toSet());
}
return resources.stream().flatMap(resource -> {
diff --git a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/model/MigrationVersion.java b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/model/MigrationVersion.java
index fbe49775..c6d06169 100644
--- a/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/model/MigrationVersion.java
+++ b/elasticsearch-evolution-core/src/main/java/com/senacor/elasticsearch/evolution/core/internal/model/MigrationVersion.java
@@ -31,7 +31,7 @@ public final class MigrationVersion implements Comparable {
/**
* Compiled pattern for matching proper version format
*/
- private static Pattern splitPattern = Pattern.compile("\\.(?=\\d)");
+ private static final Pattern splitPattern = Pattern.compile("\\.(?=\\d)");
/**
* The individual parts this version string is composed of. Ex. 1.2.3.4.0 -> [1, 2, 3, 4, 0]
diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java
index 4c6c4627..6d23f9c5 100644
--- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java
+++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/ElasticsearchEvolutionIT.java
@@ -33,6 +33,7 @@
import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.SoftAssertions.assertSoftly;
import static org.elasticsearch.client.RequestOptions.DEFAULT;
@@ -157,4 +158,70 @@ void migrate_failed_then_fixed_script_and_re_execute(String versionInfo, EsUtils
.allMatch(MigrationScriptProtocol::isSuccess);
});
}
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(ElasticsearchArgumentsProvider.class)
+ void migrate_outOfOrder_disabled_will_fail(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) {
+ ElasticsearchEvolutionConfig elasticsearchEvolutionConfig = ElasticsearchEvolution.configure()
+ .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_outOfOrder_1"));
+ String historyIndex = elasticsearchEvolutionConfig.getHistoryIndex();
+ historyRepository = new HistoryRepositoryImpl(restHighLevelClient.getLowLevelClient(), historyIndex, new MigrationScriptProtocolMapper(), 1000, objectMapper);
+
+
+ assertSoftly(softly -> {
+ softly.assertThat(elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()).migrate())
+ .as("# of successful executed scripts ")
+ .isEqualTo(2);
+ softly.assertThat(historyRepository.findAll())
+ .as("# of historyIndex entries and all are successful")
+ .hasSize(2)
+ .allMatch(MigrationScriptProtocol::isSuccess);
+ });
+ esUtils.refreshIndices();
+
+
+ elasticsearchEvolutionConfig.setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_outOfOrder_2"));
+
+
+ final ElasticsearchEvolution underTest = elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient());
+ assertThatThrownBy(underTest::migrate)
+ .isInstanceOf(MigrationException.class)
+ .hasMessage("The logged execution in the Elasticsearch-Evolution history index at position 1 is version 3 and in the same position in the given migration scripts is version 2! Out of order execution is not supported. Or maybe you have added new migration scripts in between or have to cleanup the Elasticsearch-Evolution history index manually");
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @ArgumentsSource(ElasticsearchArgumentsProvider.class)
+ void migrate_outOfOrder_enabled(String versionInfo, EsUtils esUtils, RestHighLevelClient restHighLevelClient) {
+ ElasticsearchEvolutionConfig elasticsearchEvolutionConfig = ElasticsearchEvolution.configure()
+ .setOutOfOrder(true)
+ .setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_outOfOrder_1"));
+ String historyIndex = elasticsearchEvolutionConfig.getHistoryIndex();
+ historyRepository = new HistoryRepositoryImpl(restHighLevelClient.getLowLevelClient(), historyIndex, new MigrationScriptProtocolMapper(), 1000, objectMapper);
+
+
+ assertSoftly(softly -> {
+ softly.assertThat(elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()).migrate())
+ .as("# of successful executed scripts ")
+ .isEqualTo(2);
+ softly.assertThat(historyRepository.findAll())
+ .as("# of historyIndex entries and all are successful")
+ .hasSize(2)
+ .allMatch(MigrationScriptProtocol::isSuccess);
+ });
+ esUtils.refreshIndices();
+
+
+ elasticsearchEvolutionConfig.setLocations(singletonList("classpath:es/ElasticsearchEvolutionTest/migrate_outOfOrder_2"));
+
+
+ assertSoftly(softly -> {
+ softly.assertThat(elasticsearchEvolutionConfig.load(restHighLevelClient.getLowLevelClient()).migrate())
+ .as("# of successful executed scripts ")
+ .isEqualTo(1);
+ softly.assertThat(historyRepository.findAll())
+ .as("# of historyIndex entries and all are successful")
+ .hasSize(3)
+ .allMatch(MigrationScriptProtocol::isSuccess);
+ });
+ }
}
\ No newline at end of file
diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfigTest.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfigTest.java
index 1403917f..ff5d0916 100644
--- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfigTest.java
+++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/api/config/ElasticsearchEvolutionConfigTest.java
@@ -23,94 +23,132 @@ void defaultConfig() {
@Test
void noValidEncoding() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setEncoding(null).validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setEncoding(null);
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(NullPointerException.class)
.hasMessage("encoding must not be null");
}
@Test
void noValidPlaceholders() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setPlaceholders(null).validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setPlaceholders(null);
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(NullPointerException.class)
.hasMessage("placeholders must not be null");
}
@Test
void noValidLocations() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setLocations(Collections.emptyList()).validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setLocations(Collections.emptyList());
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("locations must not be empty");
}
@Test
void noValidEsMigrationPrefix() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setEsMigrationPrefix("").validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setEsMigrationPrefix("");
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("esMigrationPrefix must not be empty");
}
@Test
void noValidEsMigrationSuffixes() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setEsMigrationSuffixes(Collections.emptyList()).validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setEsMigrationSuffixes(Collections.emptyList());
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("esMigrationSuffixes must not be empty");
}
@Test
void noValidPlaceholderPrefix() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setPlaceholderPrefix("").validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setPlaceholderPrefix("");
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("placeholderPrefix must not be empty");
}
@Test
void noValidPlaceholderSuffix() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setPlaceholderSuffix("").validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setPlaceholderSuffix("");
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("placeholderSuffix must not be empty");
}
@Test
void placeholderNameMustNotContainPlaceholderSuffix() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate()
- .setPlaceholders(Collections.singletonMap("x}x", "x"))
- .validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setPlaceholders(Collections.singletonMap("x}x", "x"));
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("placeholder name 'x}x' must not contain placeholderSuffix '}'");
}
@Test
void placeholderNameMustNotContainPlaceholderPrefix() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate()
- .setPlaceholders(Collections.singletonMap("x${x", "x"))
- .validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setPlaceholders(Collections.singletonMap("x${x", "x"));
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("placeholder name 'x${x' must not contain placeholderPrefix '${'");
}
@Test
void placeholderValueMustNotContainPlaceholderSuffix() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate()
- .setPlaceholders(Collections.singletonMap("x", "x}x"))
- .validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setPlaceholders(Collections.singletonMap("x", "x}x"));
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("placeholder value 'x}x' must not contain placeholderSuffix '}'");
}
@Test
void placeholderValueMustNotContainPlaceholderPrefix() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate()
- .setPlaceholders(Collections.singletonMap("x", "x${x"))
- .validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setPlaceholders(Collections.singletonMap("x", "x${x"));
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("placeholder value 'x${x' must not contain placeholderPrefix '${'");
}
@Test
void noValidHistoryMaxQuerySize_mustBeGreaterThan0() {
- assertThatThrownBy(() -> new ElasticsearchEvolutionConfig().validate().setHistoryMaxQuerySize(0).validate())
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setHistoryMaxQuerySize(0);
+
+ assertThatThrownBy(config::validate)
.isInstanceOf(IllegalStateException.class)
.hasMessage("historyMaxQuerySize value '0' must be greater than 0");
}
+
+ @Test
+ void baselineVersion_must_be_at_least_1() {
+ final ElasticsearchEvolutionConfig config = new ElasticsearchEvolutionConfig()
+ .setBaselineVersion("0");
+
+ assertThatThrownBy(config::validate)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("baselineVersion '0' must be at least 1");
+ }
}
}
\ No newline at end of file
diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java
index 74444d75..b2be6775 100644
--- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java
+++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplIT.java
@@ -57,7 +57,7 @@ void OK_indexDocumentIsWrittenToElasticsearch(String versionInfo, EsUtils esUtil
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepositoryMock,
0, 0, restHighLevelClient.getLowLevelClient(),
- defaultContentType, encoding, true, "1.0");
+ defaultContentType, encoding, true, "1.0", false);
MigrationScriptProtocol res = underTest.executeScript(script).getProtocol();
diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java
index 0d617462..a90dc400 100644
--- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java
+++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/internal/migration/execution/MigrationServiceImplTest.java
@@ -46,13 +46,7 @@
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.withSettings;
+import static org.mockito.Mockito.*;
/**
* @author Andreas Keefer
@@ -75,7 +69,7 @@ class waitUntilUnlocked {
void noLockExists() {
doReturn(false).when(historyRepository).isLocked();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 2000, 2000, restClient, defaultContentType, encoding, true, "1.0");
+ 2000, 2000, restClient, defaultContentType, encoding, true, "1.0", false);
assertTimeout(Duration.ofSeconds(1), underTest::waitUntilUnlocked);
@@ -88,9 +82,9 @@ void noLockExists() {
void LockExistsAndGetsReleased() {
doReturn(true, false).when(historyRepository).isLocked();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 100, 100, restClient, defaultContentType, encoding, true, "1.0");
+ 100, 100, restClient, defaultContentType, encoding, true, "1.0", false);
- assertTimeout(Duration.ofMillis(200), underTest::waitUntilUnlocked);
+ assertTimeout(Duration.ofMillis(300), underTest::waitUntilUnlocked);
InOrder order = inOrder(historyRepository);
order.verify(historyRepository, times(2)).isLocked();
@@ -104,7 +98,7 @@ class getPendingScriptsToBeExecuted {
void emptyHistory_allScriptsHaveToBeReturned() {
doReturn(new TreeSet<>()).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1");
ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0");
List parsedMigrationScripts = asList(
@@ -128,7 +122,7 @@ void scriptsAndHistoryInSync_noScriptsWillBeReturned() {
createMigrationScriptProtocol("1.1", true)
))).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
List parsedMigrationScripts = asList(
createParsedMigrationScript("1.1"),
@@ -149,7 +143,7 @@ void lastHistoryVersionWasFailing_AllScriptsInclFailedWillBeReturned() {
createMigrationScriptProtocol("1.1", false)
))).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0");
ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1");
@@ -174,7 +168,7 @@ void moreHistoryVersionsThanScripts_warningIsShownAnNoScriptsWillBeReturned() {
createMigrationScriptProtocol("1.2", false)
))).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0");
ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1");
@@ -191,13 +185,13 @@ void moreHistoryVersionsThanScripts_warningIsShownAnNoScriptsWillBeReturned() {
}
@Test
- void outOfOrderExecutionIsNotSupported() {
+ void outOfOrderExecutionIsDisabled() {
doReturn(new TreeSet<>(asList(
createMigrationScriptProtocol("1.0", true),
createMigrationScriptProtocol("1.1", true)
))).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0");
ParsedMigrationScript parsedMigrationScript1_0_1 = createParsedMigrationScript("1.0.1");
@@ -212,6 +206,72 @@ void outOfOrderExecutionIsNotSupported() {
.hasMessage("The logged execution in the Elasticsearch-Evolution history index at position 1 is version 1.1 and in the same position in the given migration scripts is version 1.0.1! Out of order execution is not supported. Or maybe you have added new migration scripts in between or have to cleanup the Elasticsearch-Evolution history index manually");
}
+ @Test
+ void outOfOrderExecutionIsEnabled_happy_path() {
+ doReturn(new TreeSet<>(asList(
+ createMigrationScriptProtocol("1.0", true),
+ createMigrationScriptProtocol("1.1", true)
+ ))).when(historyRepository).findAll();
+ MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", true);
+
+ ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0");
+ ParsedMigrationScript parsedMigrationScript1_0_1 = createParsedMigrationScript("1.0.1");
+ ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1");
+ List parsedMigrationScripts = asList(
+ parsedMigrationScript1_1,
+ parsedMigrationScript1_0_1,
+ parsedMigrationScript1_0);
+
+ final List pendingScriptsToBeExecuted = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts);
+ assertThat(pendingScriptsToBeExecuted)
+ .containsExactly(parsedMigrationScript1_0_1);
+ }
+
+ @Test
+ void outOfOrderExecutionIsEnabled_missing_executed_migration_script() {
+ doReturn(new TreeSet<>(asList(
+ createMigrationScriptProtocol("1.0", true),
+ createMigrationScriptProtocol("1.1", true)
+ ))).when(historyRepository).findAll();
+ MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", true);
+
+ ParsedMigrationScript parsedMigrationScript1_0_1 = createParsedMigrationScript("1.0.1");
+ ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1");
+ List parsedMigrationScripts = asList(
+ parsedMigrationScript1_1,
+ parsedMigrationScript1_0_1);
+
+ final List pendingScriptsToBeExecuted = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts);
+ assertThat(pendingScriptsToBeExecuted)
+ .containsExactly(parsedMigrationScript1_0_1);
+ }
+
+ @Test
+ void outOfOrderExecutionIsEnabled_execute_failed_script() {
+ doReturn(new TreeSet<>(asList(
+ createMigrationScriptProtocol("1.0", false),
+ createMigrationScriptProtocol("1.1", true)
+ ))).when(historyRepository).findAll();
+ MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", true);
+
+ ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0");
+ ParsedMigrationScript parsedMigrationScript1_0_1 = createParsedMigrationScript("1.0.1");
+ ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1");
+ List parsedMigrationScripts = asList(
+ parsedMigrationScript1_1,
+ parsedMigrationScript1_0_1,
+ parsedMigrationScript1_0);
+
+ final List pendingScriptsToBeExecuted = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts);
+ assertThat(pendingScriptsToBeExecuted)
+ .containsExactly(
+ parsedMigrationScript1_0,
+ parsedMigrationScript1_0_1);
+ }
+
@Test
void failingScriptWasEdited_shouldReturnAllScriptsInclFailing() {
doReturn(new TreeSet<>(asList(
@@ -219,7 +279,7 @@ void failingScriptWasEdited_shouldReturnAllScriptsInclFailing() {
createMigrationScriptProtocol("1.1", false, 2)
))).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0", 1);
ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1", 3);
@@ -246,7 +306,7 @@ void successfulScriptWasEdited_shouldThrowChecksumMismatchException() {
createMigrationScriptProtocol("1.1", true, 2)
))).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0", 1);
ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1", 3);
@@ -256,9 +316,9 @@ void successfulScriptWasEdited_shouldThrowChecksumMismatchException() {
assertThatThrownBy(() -> underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts))
.isInstanceOf(MigrationException.class)
- .hasMessage("The logged execution for the migration script at position 1 (V1.1__1.1.http) " +
- "has a different checksum from the given migration script! " +
- "Modifying already-executed scripts is not supported.");
+ .hasMessage("The logged execution for the migration script version 1.1 (V1.1__1.1.http) " +
+ "has a different checksum from the given migration script! " +
+ "Modifying already-executed scripts is not supported.");
}
@Test
@@ -268,7 +328,7 @@ void successfulScriptWasEdited_shouldContinueIfValidateOnMigrateIsDisabled() {
createMigrationScriptProtocol("1.1", true, 2)
))).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, false, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, false, "1.0", false);
ParsedMigrationScript parsedMigrationScript1_0 = createParsedMigrationScript("1.0", 1);
ParsedMigrationScript parsedMigrationScript1_1 = createParsedMigrationScript("1.1", 3);
@@ -278,7 +338,6 @@ void successfulScriptWasEdited_shouldContinueIfValidateOnMigrateIsDisabled() {
List res = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts);
- assertThat(res).hasSize(0);
assertThat(res).isEmpty();
InOrder order = inOrder(historyRepository);
order.verify(historyRepository).findAll();
@@ -289,7 +348,7 @@ void successfulScriptWasEdited_shouldContinueIfValidateOnMigrateIsDisabled() {
void usingABaseline_onlyScriptsWithVersionHigherThanBaselineWillBeReturned() {
doReturn(new TreeSet<>()).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "2.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "2.0", false);
List parsedMigrationScripts = asList(
createParsedMigrationScript("1.0"),
@@ -297,8 +356,9 @@ void usingABaseline_onlyScriptsWithVersionHigherThanBaselineWillBeReturned() {
List res = underTest.getPendingScriptsToBeExecuted(parsedMigrationScripts);
- assertThat(res).hasSize(1);
- assertThat(res).containsOnly(createParsedMigrationScript("2.0"));
+ assertThat(res)
+ .hasSize(1)
+ .containsOnly(createParsedMigrationScript("2.0"));
InOrder order = inOrder(historyRepository);
order.verify(historyRepository).findAll();
order.verifyNoMoreInteractions();
@@ -307,7 +367,7 @@ void usingABaseline_onlyScriptsWithVersionHigherThanBaselineWillBeReturned() {
@Test
void noPendingScriptsIfMigrationScriptListIsEmpty() {
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "2.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "2.0", false);
List res = underTest.getPendingScriptsToBeExecuted(emptyList());
@@ -325,7 +385,7 @@ void OK_resultIsSetCorrect() throws IOException {
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
MigrationScriptProtocol res = underTest.executeScript(script).getProtocol();
@@ -381,7 +441,7 @@ void OK_requestWithBody() throws IOException {
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
MigrationScriptProtocol res = underTest.executeScript(script).getProtocol();
@@ -421,7 +481,7 @@ void OK_requestWithCustomContentTypeAndDefaultCharset() throws IOException {
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
MigrationScriptProtocol res = underTest.executeScript(script).getProtocol();
@@ -460,7 +520,7 @@ void OK_requestWithCustomContentTypeAndCustomCharset() throws IOException {
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
MigrationScriptProtocol res = underTest.executeScript(script).getProtocol();
@@ -500,7 +560,7 @@ void OK_requestWithCustomHeader() throws IOException {
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
MigrationScriptProtocol res = underTest.executeScript(script).getProtocol();
@@ -536,7 +596,7 @@ void executeScript_failed_status(Exception handledError) throws IOException {
doThrow(handledError).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
ExecutionResult res = underTest.executeScript(script);
@@ -555,7 +615,7 @@ void executeScript_failed_status(int httpStatusCode) throws IOException {
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
ExecutionResult res = underTest.executeScript(script);
@@ -577,7 +637,7 @@ void executeScript_OK_status(int httpStatusCode) throws IOException {
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
MigrationScriptProtocol res = underTest.executeScript(script).getProtocol();
@@ -600,7 +660,7 @@ void allOK() throws IOException {
Response responseMock = createResponseMock(200);
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
List res = underTest.executePendingScripts(scripts);
@@ -633,7 +693,7 @@ void firstExecutionFailed() throws IOException {
Response responseMock = createResponseMock(statusCode);
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
assertThatThrownBy(() -> underTest.executePendingScripts(scripts))
.isInstanceOf(MigrationException.class)
@@ -666,7 +726,7 @@ void error_unlockWasNotSuccessful() throws IOException {
Response responseMock = createResponseMock(200);
doReturn(responseMock).when(restClient).performRequest(any());
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
assertThatThrownBy(() -> underTest.executePendingScripts(scripts))
.isInstanceOf(MigrationException.class)
@@ -696,7 +756,7 @@ void error_lockWasNotSuccessful() {
doReturn(new TreeSet<>()).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
assertThatThrownBy(() -> underTest.executePendingScripts(scripts))
.isInstanceOf(MigrationException.class)
@@ -714,7 +774,7 @@ void error_lockWasNotSuccessful() {
@Test
void emptyScriptsCollection() {
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
List res = underTest.executePendingScripts(emptyList());
@@ -733,7 +793,7 @@ void noPendingScripts_shouldNotLockRepository() {
createMigrationScriptProtocol("1.1", true)
))).when(historyRepository).findAll();
MigrationServiceImpl underTest = new MigrationServiceImpl(historyRepository,
- 0, 0, restClient, defaultContentType, encoding, true, "1.0");
+ 0, 0, restClient, defaultContentType, encoding, true, "1.0", false);
List res = underTest.executePendingScripts(scripts);
diff --git a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java
index de444477..8b6091b4 100644
--- a/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java
+++ b/elasticsearch-evolution-core/src/test/java/com/senacor/elasticsearch/evolution/core/test/EmbeddedElasticsearchExtension.java
@@ -44,22 +44,15 @@ public class EmbeddedElasticsearchExtension implements TestInstancePostProcessor
private static final Logger logger = LoggerFactory.getLogger(EmbeddedElasticsearchExtension.class);
private static final Namespace NAMESPACE = Namespace.create(ExtensionContext.class);
private static final SortedSet SUPPORTED_SEARCH_VERSIONS = Collections.unmodifiableSortedSet(new TreeSet<>(Arrays.asList(
- ofOpensearch("2.5.0"),
- ofOpensearch("2.4.1"),
- ofOpensearch("2.3.0"),
- ofOpensearch("2.2.1"),
- ofOpensearch("2.1.0"),
- ofOpensearch("2.0.1"),
- ofOpensearch("1.3.8"),
-
- ofElasticsearch("8.6.1"),
- ofElasticsearch("8.5.3"),
- ofElasticsearch("8.4.3"),
- ofElasticsearch("8.3.3"),
- ofElasticsearch("8.2.3"),
- ofElasticsearch("8.1.3"),
- ofElasticsearch("8.0.1"),
- ofElasticsearch("7.17.9")
+ ofOpensearch("2.11.0"),
+ ofOpensearch("2.10.0"),
+ ofOpensearch("2.9.0"),
+ ofOpensearch("1.3.12"),
+
+ ofElasticsearch("8.11.1"),
+ ofElasticsearch("8.10.4"),
+ ofElasticsearch("8.9.1"),
+ ofElasticsearch("7.17.15")
)));
@Override
@@ -195,6 +188,7 @@ public static SearchContainer ofOpensearch(String version) {
return SearchContainer.builder()
.vendor("Opensearch")
.vendorShort("OS")
+ // dockerhub and public.ecr.aws can run into rate limit, so stay with quay.io
.containerImage("quay.io/xtermi2/opensearch")
.version(version)
.env(ImmutableMap.of(
diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V001.00__createTemplateWithIndexMapping.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V001.00__createTemplateWithIndexMapping.http
new file mode 100644
index 00000000..d236dfd9
--- /dev/null
+++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V001.00__createTemplateWithIndexMapping.http
@@ -0,0 +1,35 @@
+PUT _template/test_1
+Content-Type: application/json
+
+{
+ "index_patterns": [
+ "test_*"
+ ],
+ "order": 1,
+ "version": 1,
+ "settings": {
+ "number_of_shards": 1
+ },
+ "mappings": {
+ "dynamic": "strict",
+ "properties": {
+ "doc": {
+ "dynamic": false,
+ "properties": {}
+ },
+ "searchable": {
+ "dynamic": false,
+ "properties": {
+ "version": {
+ "type": "keyword",
+ "ignore_above": 20,
+ "similarity": "boolean"
+ },
+ "locked": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V003.00__extendIndexMappingViaTemplate.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V003.00__extendIndexMappingViaTemplate.http
new file mode 100644
index 00000000..b62cdfdd
--- /dev/null
+++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_1/V003.00__extendIndexMappingViaTemplate.http
@@ -0,0 +1,38 @@
+PUT /_template/test_2
+Content-Type: application/json
+
+{
+ "index_patterns": [
+ "test_*"
+ ],
+ "order": 2,
+ "version": 2,
+ "mappings": {
+ "properties": {
+ "searchable": {
+ "properties": {
+// new field
+ "success": {
+ "type": "boolean"
+ },
+// Multi-index: additional index with type text to already existing keyword index
+ "version": {
+ "type": "keyword",
+ "ignore_above": 20,
+ "similarity": "boolean",
+ "fields": {
+ "text": {
+ "type": "text",
+ "similarity": "boolean"
+ },
+ "bm25": {
+ "type": "text",
+ "similarity": "BM25"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V001.00__createTemplateWithIndexMapping.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V001.00__createTemplateWithIndexMapping.http
new file mode 100644
index 00000000..d236dfd9
--- /dev/null
+++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V001.00__createTemplateWithIndexMapping.http
@@ -0,0 +1,35 @@
+PUT _template/test_1
+Content-Type: application/json
+
+{
+ "index_patterns": [
+ "test_*"
+ ],
+ "order": 1,
+ "version": 1,
+ "settings": {
+ "number_of_shards": 1
+ },
+ "mappings": {
+ "dynamic": "strict",
+ "properties": {
+ "doc": {
+ "dynamic": false,
+ "properties": {}
+ },
+ "searchable": {
+ "dynamic": false,
+ "properties": {
+ "version": {
+ "type": "keyword",
+ "ignore_above": 20,
+ "similarity": "boolean"
+ },
+ "locked": {
+ "type": "boolean"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V002.00__addDocument.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V002.00__addDocument.http
new file mode 100644
index 00000000..4f18ba0a
--- /dev/null
+++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V002.00__addDocument.http
@@ -0,0 +1,17 @@
+PUT /test_1/_doc/1?refresh
+Content-Type: application/json
+
+{
+ "searchable": {
+ "version": "1",
+ "locked": false
+ },
+ "doc": {
+ "version": "1",
+ "locked": false,
+ "success": true,
+ "a": "a a a",
+ "b": true,
+ "c": "c"
+ }
+}
\ No newline at end of file
diff --git a/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V003.00__extendIndexMappingViaTemplate.http b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V003.00__extendIndexMappingViaTemplate.http
new file mode 100644
index 00000000..b62cdfdd
--- /dev/null
+++ b/elasticsearch-evolution-core/src/test/resources/es/ElasticsearchEvolutionTest/migrate_outOfOrder_2/V003.00__extendIndexMappingViaTemplate.http
@@ -0,0 +1,38 @@
+PUT /_template/test_2
+Content-Type: application/json
+
+{
+ "index_patterns": [
+ "test_*"
+ ],
+ "order": 2,
+ "version": 2,
+ "mappings": {
+ "properties": {
+ "searchable": {
+ "properties": {
+// new field
+ "success": {
+ "type": "boolean"
+ },
+// Multi-index: additional index with type text to already existing keyword index
+ "version": {
+ "type": "keyword",
+ "ignore_above": 20,
+ "similarity": "boolean",
+ "fields": {
+ "text": {
+ "type": "text",
+ "similarity": "boolean"
+ },
+ "bm25": {
+ "type": "text",
+ "similarity": "BM25"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/mvnw b/mvnw
index 5643201c..8d937f4c 100755
--- a/mvnw
+++ b/mvnw
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven Start Up Batch script
+# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
@@ -27,7 +27,6 @@
#
# Optional ENV vars
# -----------------
-# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -54,7 +53,7 @@ fi
cygwin=false;
darwin=false;
mingw=false
-case "`uname`" in
+case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
@@ -62,9 +61,9 @@ case "`uname`" in
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
- export JAVA_HOME="`/usr/libexec/java_home`"
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
- export JAVA_HOME="/Library/Java/Home"
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
@@ -72,68 +71,38 @@ esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
- JAVA_HOME=`java-config --jre-home`
+ JAVA_HOME=$(java-config --jre-home)
fi
fi
-if [ -z "$M2_HOME" ] ; then
- ## resolve links - $0 may be a link to maven's home
- PRG="$0"
-
- # need this for relative symlinks
- while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG="`dirname "$PRG"`/$link"
- fi
- done
-
- saveddir=`pwd`
-
- M2_HOME=`dirname "$PRG"`/..
-
- # make it fully qualified
- M2_HOME=`cd "$M2_HOME" && pwd`
-
- cd "$saveddir"
- # echo Using m2 at $M2_HOME
-fi
-
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
- [ -n "$M2_HOME" ] &&
- M2_HOME="`(cd "$M2_HOME"; pwd)`"
- [ -n "$JAVA_HOME" ] &&
- JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
- javaExecutable="`which javac`"
- if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
- readLink=`which readlink`
- if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
- javaHome="`dirname \"$javaExecutable\"`"
- javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
- javaExecutable="`readlink -f \"$javaExecutable\"`"
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
- javaHome="`dirname \"$javaExecutable\"`"
- javaHome=`expr "$javaHome" : '\(.*\)/bin'`
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
@@ -149,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then
JAVACMD="$JAVA_HOME/bin/java"
fi
else
- JAVACMD="`\\unset -f command; \\command -v java`"
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
@@ -163,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
-CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
-
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
-
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
@@ -184,96 +150,99 @@ find_maven_basedir() {
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
- wdir=`cd "$wdir/.."; pwd`
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
- echo "${basedir}"
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
- echo "$(tr -s '\n' ' ' < "$1")"
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
fi
}
-BASE_DIR=`find_maven_basedir "$(pwd)"`
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
-if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found .mvn/wrapper/maven-wrapper.jar"
- fi
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
- fi
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
if [ -n "$MVNW_REPOURL" ]; then
- jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
else
- jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
fi
- while IFS="=" read key value; do
- case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac
- done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Downloading from: $jarUrl"
- fi
- wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
if $cygwin; then
- wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found wget ... using wget"
- fi
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
- wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Found curl ... using curl"
- fi
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
- curl -o "$wrapperJarPath" "$jarUrl" -f
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
- curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
-
else
- if [ "$MVNW_VERBOSE" = true ]; then
- echo "Falling back to using Java to download"
- fi
- javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
- javaClass=`cygpath --path --windows "$javaClass"`
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
fi
- if [ -e "$javaClass" ]; then
- if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Compiling MavenWrapperDownloader.java ..."
- fi
- # Compiling the Java class
- ("$JAVA_HOME/bin/javac" "$javaClass")
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
fi
- if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
- # Running the downloader
- if [ "$MVNW_VERBOSE" = true ]; then
- echo " - Running MavenWrapperDownloader.java ..."
- fi
- ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
@@ -282,35 +251,58 @@ fi
# End of extension
##########################################################################################
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
-if [ "$MVNW_VERBOSE" = true ]; then
- echo $MAVEN_PROJECTBASEDIR
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
fi
+
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
- [ -n "$M2_HOME" ] &&
- M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
- JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
- CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
- MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
-MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
- "-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
index 8a15b7f3..c4586b56 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -18,13 +18,12 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven Start Up Batch script
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
-@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@@ -120,10 +119,10 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
- IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@@ -134,11 +133,11 @@ if exist %WRAPPER_JAR% (
)
) else (
if not "%MVNW_REPOURL%" == "" (
- SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
- echo Downloading from: %DOWNLOAD_URL%
+ echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
@@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% (
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
- "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
@@ -154,6 +153,24 @@ if exist %WRAPPER_JAR% (
)
@REM End of extension
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
diff --git a/pom.xml b/pom.xml
index 74c812f8..2d8f00ef 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,12 @@
com.senacor.elasticsearch.evolution
elasticsearch-evolution-parent
- 0.4.2
+ 0.4.3
pom
org.springframework.boot
spring-boot-dependencies
- 2.7.8
+ 2.7.17
elasticsearch-evolution
@@ -94,17 +94,19 @@
4.3.0
0.8.8
- 3.0.1
- 3.4.1
+ 3.1.0
+ 3.6.2
1.6.13
- 2.11.0
+ 2.15.0
7.5.2
- 0.9.12
- 1.17.6
- 1.18.26
+ 0.10.2
+ 1.19.1
+ 1.18.30
+
+ 1.14.9
diff --git a/spring-boot-starter-elasticsearch-evolution/pom.xml b/spring-boot-starter-elasticsearch-evolution/pom.xml
index b7c875ab..95585efb 100644
--- a/spring-boot-starter-elasticsearch-evolution/pom.xml
+++ b/spring-boot-starter-elasticsearch-evolution/pom.xml
@@ -6,7 +6,7 @@
com.senacor.elasticsearch.evolution
elasticsearch-evolution-parent
- 0.4.2
+ 0.4.3
../
spring-boot-starter-elasticsearch-evolution
diff --git a/tests/migration-scripts/pom.xml b/tests/migration-scripts/pom.xml
index eec8dde7..2b46b5d0 100644
--- a/tests/migration-scripts/pom.xml
+++ b/tests/migration-scripts/pom.xml
@@ -4,7 +4,7 @@
4.0.0
com.senacor.elasticsearch.evolution
migration-scripts
- 0.4.2
+ 0.4.3
jar containing migration files
jar
diff --git a/tests/pom.xml b/tests/pom.xml
index 694435cc..7a499afe 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -6,7 +6,7 @@
com.senacor.elasticsearch.evolution
elasticsearch-evolution-parent
- 0.4.2
+ 0.4.3
../
tests
@@ -37,6 +37,7 @@
test-spring-boot-3.0-scriptsInJarFile
+ test-spring-boot-3.1
diff --git a/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml b/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml
index 854d6026..af66ca41 100644
--- a/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml
+++ b/tests/test-spring-boot-2.1-scriptsInJarFile/pom.xml
@@ -10,15 +10,15 @@
com.senacor.elasticsearch.evolution
test-spring-boot-2.1-scriptsInJarFile
- 0.4.2
+ 0.4.3
Demo project for Spring Boot
1.8
- 2.11.0
+ 2.15.0
7.5.2
- 1.17.6
+ 1.19.1
diff --git a/tests/test-spring-boot-2.2/pom.xml b/tests/test-spring-boot-2.2/pom.xml
index 324939f5..137a209c 100644
--- a/tests/test-spring-boot-2.2/pom.xml
+++ b/tests/test-spring-boot-2.2/pom.xml
@@ -10,15 +10,15 @@
com.senacor.elasticsearch.evolution
test-spring-boot-2.2
- 0.4.2
+ 0.4.3
Demo project for Spring Boot
1.8
- 2.11.0
+ 2.15.0
7.5.2
- 1.17.6
+ 1.19.1
@@ -77,7 +77,6 @@
org.jacoco
jacoco-maven-plugin
-
0.8.8
diff --git a/tests/test-spring-boot-2.3/pom.xml b/tests/test-spring-boot-2.3/pom.xml
index 96d9f8c5..44bc4e82 100644
--- a/tests/test-spring-boot-2.3/pom.xml
+++ b/tests/test-spring-boot-2.3/pom.xml
@@ -10,15 +10,15 @@
com.senacor.elasticsearch.evolution
test-spring-boot-2.3
- 0.4.2
+ 0.4.3
Demo project for Spring Boot
1.8
- 2.11.0
+ 2.15.0
7.5.2
- 1.17.6
+ 1.19.1
diff --git a/tests/test-spring-boot-2.4/pom.xml b/tests/test-spring-boot-2.4/pom.xml
index 5cb6ffe9..cd99aa2a 100644
--- a/tests/test-spring-boot-2.4/pom.xml
+++ b/tests/test-spring-boot-2.4/pom.xml
@@ -10,15 +10,15 @@
com.senacor.elasticsearch.evolution
test-spring-boot-2.4
- 0.4.2
+ 0.4.3
Demo project for Spring Boot
1.8
- 2.11.0
+ 2.15.0
7.5.2
- 1.17.6
+ 1.19.1
diff --git a/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml b/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml
index d5f9ba04..d5337653 100644
--- a/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml
+++ b/tests/test-spring-boot-2.5-scriptsInJarFile/pom.xml
@@ -10,15 +10,15 @@
com.senacor.elasticsearch.evolution
test-spring-boot-2.5-scriptsInJarFile
- 0.4.2
+ 0.4.3
Demo project for Spring Boot
1.8
- 2.11.0
+ 2.15.0
7.5.2
- 1.17.6
+ 1.19.1
diff --git a/tests/test-spring-boot-2.6/pom.xml b/tests/test-spring-boot-2.6/pom.xml
index 8b35cb3a..1278176b 100644
--- a/tests/test-spring-boot-2.6/pom.xml
+++ b/tests/test-spring-boot-2.6/pom.xml
@@ -10,15 +10,15 @@
com.senacor.elasticsearch.evolution
test-spring-boot-2.6
- 0.4.2
+ 0.4.3
Demo project for Spring Boot
1.8
- 2.11.0
+ 2.15.0
7.5.2
- 1.17.6
+ 1.19.1
diff --git a/tests/test-spring-boot-2.7/pom.xml b/tests/test-spring-boot-2.7/pom.xml
index e61d8a04..f0c652c6 100644
--- a/tests/test-spring-boot-2.7/pom.xml
+++ b/tests/test-spring-boot-2.7/pom.xml
@@ -5,20 +5,20 @@
org.springframework.boot
spring-boot-starter-parent
- 2.7.8
+ 2.7.17
com.senacor.elasticsearch.evolution
test-spring-boot-2.7
- 0.4.2
+ 0.4.3
Demo project for Spring Boot
1.8
- 2.11.0
+ 2.15.0
7.5.2
- 1.17.6
+ 1.19.1
diff --git a/tests/test-spring-boot-3.0-scriptsInJarFile/pom.xml b/tests/test-spring-boot-3.0-scriptsInJarFile/pom.xml
index 188cc04e..9cf1bf06 100644
--- a/tests/test-spring-boot-3.0-scriptsInJarFile/pom.xml
+++ b/tests/test-spring-boot-3.0-scriptsInJarFile/pom.xml
@@ -5,20 +5,20 @@
org.springframework.boot
spring-boot-starter-parent
- 3.0.2
+ 3.0.12
com.senacor.elasticsearch.evolution
test-spring-boot-3.0-scriptsInJarFile
- 0.4.2
+ 0.4.3
Demo project for Spring Boot
17
- 2.11.0
+ 2.15.0
7.5.2
- 1.17.5
+ 1.19.1
diff --git a/tests/test-spring-boot-3.1/pom.xml b/tests/test-spring-boot-3.1/pom.xml
new file mode 100644
index 00000000..a3a0e6ae
--- /dev/null
+++ b/tests/test-spring-boot-3.1/pom.xml
@@ -0,0 +1,107 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.1.5
+
+
+ com.senacor.elasticsearch.evolution
+ test-spring-boot-3.1
+ 0.4.3
+ Demo project for Spring Boot
+
+
+ 17
+
+ 2.15.0
+ 7.5.2
+ 1.19.1
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+ ${project.groupId}
+ spring-boot-starter-elasticsearch-evolution
+ ${project.version}
+
+
+
+
+ org.testcontainers
+ elasticsearch
+ ${testcontainers.elasticsearch.version}
+ test
+
+
+ commons-io
+ commons-io
+ ${commons-io.version}
+ test
+
+
+
+
+
+ oss.sonatype.org-snapshot
+ https://oss.sonatype.org/content/repositories/snapshots
+
+ false
+
+
+ true
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+ org.jacoco
+ jacoco-maven-plugin
+
+ 0.8.8
+
+
+ prepare-agent
+
+ prepare-agent
+
+
+
+ report
+ post-integration-test
+
+ report
+
+
+
+
+
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
+
diff --git a/tests/test-spring-boot-3.1/src/main/java/com/senacor/elasticsearch/evolution/springboot31/Application.java b/tests/test-spring-boot-3.1/src/main/java/com/senacor/elasticsearch/evolution/springboot31/Application.java
new file mode 100644
index 00000000..54d8a3f3
--- /dev/null
+++ b/tests/test-spring-boot-3.1/src/main/java/com/senacor/elasticsearch/evolution/springboot31/Application.java
@@ -0,0 +1,14 @@
+package com.senacor.elasticsearch.evolution.springboot31;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+
+
+}
\ No newline at end of file
diff --git a/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http b/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http
new file mode 100644
index 00000000..fbd7df47
--- /dev/null
+++ b/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.00__createTemplateWithIndexMapping.http
@@ -0,0 +1,35 @@
+PUT _template/test_1
+Content-Type: application/json
+
+{
+ "index_patterns" : [
+ "test_*"
+ ],
+ "order" : 1,
+ "version" : 1,
+ "settings" : {
+ "number_of_shards" : 1
+ },
+ "mappings" : {
+ "dynamic" : "strict",
+ "properties" : {
+ "doc" : {
+ "dynamic" : false,
+ "properties" : {}
+ },
+ "searchable" : {
+ "dynamic" : false,
+ "properties" : {
+ "version" : {
+ "type" : "keyword",
+ "ignore_above" : 20,
+ "similarity" : "boolean"
+ },
+ "locked" : {
+ "type" : "boolean"
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.01__addDocument.http b/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.01__addDocument.http
new file mode 100644
index 00000000..4f18ba0a
--- /dev/null
+++ b/tests/test-spring-boot-3.1/src/main/resources/es/migration/V001.01__addDocument.http
@@ -0,0 +1,17 @@
+PUT /test_1/_doc/1?refresh
+Content-Type: application/json
+
+{
+ "searchable": {
+ "version": "1",
+ "locked": false
+ },
+ "doc": {
+ "version": "1",
+ "locked": false,
+ "success": true,
+ "a": "a a a",
+ "b": true,
+ "c": "c"
+ }
+}
\ No newline at end of file
diff --git a/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/ApplicationTests.java b/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/ApplicationTests.java
new file mode 100644
index 00000000..80f85dc6
--- /dev/null
+++ b/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/ApplicationTests.java
@@ -0,0 +1,66 @@
+package com.senacor.elasticsearch.evolution.springboot31;
+
+import com.github.dockerjava.api.command.InspectContainerResponse;
+import org.apache.http.HttpHost;
+import org.elasticsearch.client.RestClient;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.context.TestConfiguration;
+import org.springframework.context.annotation.Bean;
+import org.testcontainers.elasticsearch.ElasticsearchContainer;
+import org.testcontainers.utility.DockerImageName;
+
+import java.util.Collections;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SpringBootTest(properties = {"spring.elasticsearch.uris=http://localhost:" + ApplicationTests.ELASTICSEARCH_PORT})
+class ApplicationTests {
+
+ static final int ELASTICSEARCH_PORT = 18773;
+
+ @Autowired
+ private EsUtils esUtils;
+
+ @Test
+ void contextLoads() {
+ esUtils.refreshIndices();
+
+ List documents = esUtils.fetchAllDocuments("test_1");
+
+ assertThat(documents).hasSize(1);
+ }
+
+ @TestConfiguration
+ static class Config {
+ @Bean(destroyMethod = "stop")
+ public ElasticsearchContainer elasticsearchContainer(@Value("${elasticsearch.version:7.5.2}") String esVersion) {
+ ElasticsearchContainer container = new ElasticsearchContainer(DockerImageName
+ .parse("docker.elastic.co/elasticsearch/elasticsearch")
+ .withTag(esVersion)) {
+ @Override
+ protected void containerIsStarted(InspectContainerResponse containerInfo) {
+ // since testcontainers 1.17 it detects if ES 8.x is running and copies a certificate in this case
+ // but we don't want security
+ }
+ }
+ .withEnv("ES_JAVA_OPTS", "-Xms128m -Xmx128m")
+ // since elasticsearch 8 security / https is enabled per default - but for testing it should be disabled
+ .withEnv("xpack.security.enabled", "false")
+ .withEnv("cluster.routing.allocation.disk.watermark.low", "97%")
+ .withEnv("cluster.routing.allocation.disk.watermark.high", "98%")
+ .withEnv("cluster.routing.allocation.disk.watermark.flood_stage", "99%");
+ container.setPortBindings(Collections.singletonList(ELASTICSEARCH_PORT + ":9200"));
+ container.start();
+ return container;
+ }
+
+ @Bean
+ public EsUtils esUtils(ElasticsearchContainer elasticsearchContainer) {
+ return new EsUtils(RestClient.builder(HttpHost.create(elasticsearchContainer.getHttpHostAddress())).build());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/EsUtils.java b/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/EsUtils.java
new file mode 100644
index 00000000..4c69d697
--- /dev/null
+++ b/tests/test-spring-boot-3.1/src/test/java/com/senacor/elasticsearch/evolution/springboot31/EsUtils.java
@@ -0,0 +1,72 @@
+package com.senacor.elasticsearch.evolution.springboot31;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.commons.io.IOUtils;
+import org.elasticsearch.client.Request;
+import org.elasticsearch.client.Response;
+import org.elasticsearch.client.RestClient;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+/**
+ * @author Andreas Keefer
+ */
+public class EsUtils {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ private final RestClient restClient;
+
+ public EsUtils(RestClient restClient) {
+ this.restClient = restClient;
+ }
+
+ public void refreshIndices() {
+ try {
+ restClient.performRequest(new Request("GET", "/_refresh"));
+ } catch (IOException e) {
+ throw new IllegalStateException("refreshIndices failed", e);
+ }
+ }
+
+ public List fetchAllDocuments(String index) {
+ try {
+ Request post = new Request("POST", "/" + index + "/_search");
+ post.setJsonEntity("{" +
+ " \"query\": {" +
+ " \"match_all\": {}" +
+ " }" +
+ "}");
+ Response response = restClient.performRequest(post);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode < 200 || statusCode >= 300) {
+ throw new IllegalStateException("fetchAllDocuments(" + index + ") failed with HTTP status " +
+ statusCode + ": " + response.toString());
+ }
+ String body = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
+
+ return parseDocuments(body)
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ throw new IllegalStateException("fetchAllDocuments(" + index + ") failed", e);
+ }
+ }
+
+ private Stream parseDocuments(String body) {
+ try {
+ JsonNode jsonNode = OBJECT_MAPPER.readTree(body);
+ return StreamSupport.stream(jsonNode.get("hits").get("hits").spliterator(), false)
+ .map(hitNode -> hitNode.get("_source"))
+ .map(JsonNode::toString);
+ } catch (IOException e) {
+ throw new IllegalStateException("parseDocuments failed. body=" + body, e);
+ }
+ }
+}
+