MavenVersionChecker.java
/*-
* ========================LICENSE_START=================================
* flyway-commandline
* ========================================================================
* Copyright (C) 2010 - 2025 Red Gate Software Ltd
* ========================================================================
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =========================LICENSE_END==================================
*/
package org.flywaydb.commandline;
import static lombok.AccessLevel.PACKAGE;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse.BodyHandlers;
import java.time.Duration;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import lombok.CustomLog;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import org.flywaydb.core.api.MigrationVersion;
import org.flywaydb.core.internal.license.VersionPrinter;
import org.flywaydb.core.internal.util.FlywayDbWebsiteLinks;
@CustomLog
@RequiredArgsConstructor(access = PACKAGE)
class MavenVersionChecker {
private final String flywayUrl;
private final Supplier<MigrationVersion> currentVersionSupplier;
MavenVersionChecker() {
this(FLYWAY_URL, MavenVersionChecker::getCurrentVersion);
}
@Setter
@Getter
@NoArgsConstructor
private static class MavenVersioning {
private String release;
}
@Setter
@Getter
@NoArgsConstructor
private static class MavenMetadata {
private MavenVersioning versioning;
}
private static final int CONNECT_TIMEOUT_MS = 1000;
private static final int REQUEST_TIMEOUT_MS = 8000;
private static final String FLYWAY_URL =
"https://repo1.maven.org/maven2/org/flywaydb/flyway-core/maven-metadata.xml";
String checkForVersionUpdates() {
try {
final var metadata = getMavenMetadata();
final var current = currentVersionSupplier.get();
final var latest = MigrationVersion.fromVersion(metadata.getVersioning().getRelease());
if (current.compareTo(latest) < 0) {
return getMessage(latest);
}
} catch (final Exception e) {
LOG.debug("Unable to check for updates: " + e.getMessage());
}
return null;
}
private MavenMetadata getMavenMetadata()
throws URISyntaxException, ExecutionException, InterruptedException, JsonProcessingException {
final var url = new URI(flywayUrl);
final var client = HttpClient.newBuilder().connectTimeout(Duration.ofMillis(CONNECT_TIMEOUT_MS)).build();
final var request = HttpRequest.newBuilder(url)
.GET()
.header("User-Agent", "Flyway")
.timeout(Duration.ofMillis(REQUEST_TIMEOUT_MS))
.build();
final var response = client.sendAsync(request, BodyHandlers.ofString())
.orTimeout(REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS)
.get();
final var xmlMapper = new XmlMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
return xmlMapper.readValue(response.body(), MavenMetadata.class);
}
private static MigrationVersion getCurrentVersion() {
return MigrationVersion.fromVersion(VersionPrinter.getVersion());
}
private static String getMessage(final MigrationVersion latest) {
final var message = "\nA more recent version of Flyway is available. Find out more about Flyway "
+ latest
+ " at "
+ FlywayDbWebsiteLinks.STAYING_UP_TO_DATE
+ "\n";
final var border = "-".repeat(message.trim().length());
return "\n" + border + message + border;
}
}