diff --git a/README.md b/README.md index 1cab680..5fddfe6 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Starts a Redis server and makes the port available as Spring Boot environment property. Stops the server when the Spring context is destroyed. -Requires Java 8 or later. Uses [embedded-redis](https://github.com/ozimov/embedded-redis) and [Spring Boot](https://spring.io/projects/spring-boot). +Requires Java 8 or later. Uses [embedded-redis](https://github.com/signalapp/embedded-redis) and [Spring Boot](https://spring.io/projects/spring-boot). ## Installation diff --git a/build.gradle.kts b/build.gradle.kts index fe61edc..a1edc4e 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,7 +40,7 @@ dependencies { implementation("org.springframework.boot:spring-boot-starter-test:$springBootVersion") { exclude(group = "com.vaadin.external.google") } - implementation("it.ozimov:embedded-redis:$embeddedRedisVersion") { + implementation("org.signal:embedded-redis:$embeddedRedisVersion") { exclude(group = "org.slf4j", module = "slf4j-simple") } testImplementation("org.springframework.boot:spring-boot-starter:$springBootVersion") diff --git a/gradle.properties b/gradle.properties index 251f764..988e7f5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ org.gradle.warning.mode=all org.gradle.jvmargs=-XX:MaxMetaspaceSize=2G springBootVersion=2.5.2 -embeddedRedisVersion=0.7.3 +embeddedRedisVersion=0.8.3 lettuceVersion=6.1.4.RELEASE jUnitVersion=5.7.0 kotlin.version=1.5.20 diff --git a/src/main/kotlin/com/asarkar/spring/test/redis/EmbeddedRedisApplicationListener.kt b/src/main/kotlin/com/asarkar/spring/test/redis/EmbeddedRedisApplicationListener.kt index 9eb4ae5..25e8b03 100644 --- a/src/main/kotlin/com/asarkar/spring/test/redis/EmbeddedRedisApplicationListener.kt +++ b/src/main/kotlin/com/asarkar/spring/test/redis/EmbeddedRedisApplicationListener.kt @@ -5,7 +5,6 @@ import org.springframework.context.ApplicationListener import org.springframework.core.Ordered import org.springframework.core.annotation.Order import org.springframework.core.env.MapPropertySource -import org.springframework.util.SocketUtils @Order(Ordered.LOWEST_PRECEDENCE) open class EmbeddedRedisApplicationListener : ApplicationListener { diff --git a/src/main/kotlin/com/asarkar/spring/test/redis/SocketUtils.kt b/src/main/kotlin/com/asarkar/spring/test/redis/SocketUtils.kt new file mode 100644 index 0000000..33d9150 --- /dev/null +++ b/src/main/kotlin/com/asarkar/spring/test/redis/SocketUtils.kt @@ -0,0 +1,127 @@ +package com.asarkar.spring.test.redis + +import org.springframework.util.Assert +import java.net.DatagramSocket +import java.net.InetAddress +import java.net.ServerSocket +import java.util.Random +import javax.net.ServerSocketFactory + +/** + * Simple utility methods for working with network sockets for example, + * for finding available ports on `localhost`. + * + * Within this class, a TCP port refers to a port for a [ServerSocket]; + * whereas, a UDP port refers to a port for a [DatagramSocket]. + * + * org.springframework.util.SocketUtils was removed in Spring 6. + * From Springboot 3.2.3 version, SocketUtils cannot be used. + * org.springframework.test.util.TestSocketUtils can be used as an alternative, but embedded-redis-spring has to support older versions, so org.springframework.util.SocketUtils was copied. + * + * @author fennec-fox + * @since 1.1.2 + */ +object SocketUtils { + /** + * The default minimum value for port ranges used when finding an available + * socket port. + */ + private const val PORT_RANGE_MIN: Int = 1024 + + /** + * The default maximum value for port ranges used when finding an available + * socket port. + */ + const val PORT_RANGE_MAX: Int = 65535 + + private val random = Random(System.nanoTime()) + + /** + * Find an available TCP port randomly selected from the range + * [`minPort`, `maxPort`]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + /** + * Find an available TCP port randomly selected from the range + * [`minPort`, {@value #PORT_RANGE_MAX}]. + * @param minPort the minimum port number + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + /** + * Find an available TCP port randomly selected from the range + * [{@value #PORT_RANGE_MIN}, {@value #PORT_RANGE_MAX}]. + * @return an available TCP port number + * @throws IllegalStateException if no available port could be found + */ + @JvmOverloads + fun findAvailableTcpPort(minPort: Int = PORT_RANGE_MIN, maxPort: Int = PORT_RANGE_MAX): Int { + return SocketType.TCP.findAvailablePort(minPort, maxPort) + } + + private enum class SocketType { + TCP { + override fun isPortAvailable(port: Int): Boolean { + try { + val serverSocket = ServerSocketFactory.getDefault().createServerSocket( + port, + 1, + InetAddress.getByName("localhost") + ) + serverSocket.close() + return true + } catch (ex: Exception) { + return false + } + } + }; + + /** + * Determine if the specified port for this `SocketType` is + * currently available on `localhost`. + */ + protected abstract fun isPortAvailable(port: Int): Boolean + + /** + * Find a pseudo-random port number within the range + * [`minPort`, `maxPort`]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return a random port number within the specified range + */ + private fun findRandomPort(minPort: Int, maxPort: Int): Int { + val portRange = maxPort - minPort + return minPort + random.nextInt(portRange + 1) + } + + /** + * Find an available port for this `SocketType`, randomly selected + * from the range [`minPort`, `maxPort`]. + * @param minPort the minimum port number + * @param maxPort the maximum port number + * @return an available port number for this socket type + * @throws IllegalStateException if no available port could be found + */ + fun findAvailablePort(minPort: Int, maxPort: Int): Int { + Assert.isTrue(minPort > 0, "'minPort' must be greater than 0") + Assert.isTrue(maxPort >= minPort, "'maxPort' must be greater than or equal to 'minPort'") + Assert.isTrue(maxPort <= PORT_RANGE_MAX, "'maxPort' must be less than or equal to $PORT_RANGE_MAX") + + val portRange = maxPort - minPort + var candidatePort: Int + var searchCounter = 0 + do { + check(searchCounter <= portRange) { + "Could not find an available $name port in the range [$minPort, $maxPort] after $searchCounter attempts" + } + candidatePort = findRandomPort(minPort, maxPort) + searchCounter++ + } while (!isPortAvailable(candidatePort)) + + return candidatePort + } + } +}