From 37b9647aa845308648fa1450cc1a763f9bb52c94 Mon Sep 17 00:00:00 2001
From: Valery Kharseko <vharseko@3a-systems.ru>
Date: Fri, 31 Jul 2026 17:01:46 +0000
Subject: [PATCH] [#797] Retry the deletion of the embedded server temporary directory on close() (#798)
---
opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/Config.java | 98 +++++++++++++++++++++++++++++++++++++++++++++++--
1 files changed, 94 insertions(+), 4 deletions(-)
diff --git a/opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/Config.java b/opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/Config.java
index 143c867..fe314c8 100644
--- a/opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/Config.java
+++ b/opendj-embedded/src/main/java/org/openidentityplatform/opendj/embedded/Config.java
@@ -11,7 +11,7 @@
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions Copyright [year] [name of copyright owner]".
*
- * Copyright 2024 3A Systems LLC.
+ * Copyright 2024-2026 3A Systems LLC.
*/
package org.openidentityplatform.opendj.embedded;
@@ -23,9 +23,9 @@
public class Config {
private final String CONFIG_PREFIX = Config.class.getPackage().getName();
- private int port = Integer.parseInt(System.getProperty(CONFIG_PREFIX + ".port", "1389"));
+ private int port = intProperty(CONFIG_PREFIX + ".port", "1389");
- private int adminPort = Integer.parseInt(System.getProperty(CONFIG_PREFIX + ".admin_port", "4444"));
+ private int adminPort = intProperty(CONFIG_PREFIX + ".admin_port", "4444");
private String adminPassword = System.getProperty(CONFIG_PREFIX + ".password", "passw0rd");
@@ -33,7 +33,7 @@
private String backendType = System.getProperty(CONFIG_PREFIX + ".backend", "je");
- private int jmxPort = Integer.parseInt(System.getProperty(CONFIG_PREFIX + ".jmx_port", "1689"));
+ private int jmxPort = intProperty(CONFIG_PREFIX + ".jmx_port", "1689");
private String ldifSchema = System.getProperty(CONFIG_PREFIX + ".ldif.schema");
@@ -41,6 +41,64 @@
private Set<String> skipSet = new HashSet<>(Arrays.asList(System.getProperty(CONFIG_PREFIX + ".skip", ",ou=sample-skip-group,").toLowerCase().split(";")));
+ private long deleteTimeout = timeoutProperty(CONFIG_PREFIX + ".delete_timeout", "10000");
+
+ /**
+ * Returns the value of a system property holding a number.
+ * <p>
+ * These fields are initialized when the instance is created, so an unhandled
+ * {@link NumberFormatException} would surface from the constructor of
+ * {@link EmbeddedOpenDJ} saying only which string was rejected, without naming the
+ * property that carried it.
+ *
+ * @param name
+ * the name of the system property
+ * @param defaultValue
+ * the value used when the property is not set
+ * @return the value of the property, or {@code defaultValue} when it is not set
+ * @throws IllegalArgumentException
+ * If the value of the property is not a number.
+ */
+ private static int intProperty(String name, String defaultValue) {
+ final String value = System.getProperty(name, defaultValue);
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ throw invalidProperty(name, value, e);
+ }
+ }
+
+ /**
+ * Returns the value of a system property holding a duration in milliseconds.
+ *
+ * @param name
+ * the name of the system property
+ * @param defaultValue
+ * the value used when the property is not set
+ * @return the value of the property, or {@code defaultValue} when it is not set
+ * @throws IllegalArgumentException
+ * If the value of the property is not a number, or is negative.
+ */
+ private static long timeoutProperty(String name, String defaultValue) {
+ final String value = System.getProperty(name, defaultValue);
+ final long timeout;
+ try {
+ timeout = Long.parseLong(value);
+ } catch (NumberFormatException e) {
+ throw invalidProperty(name, value, e);
+ }
+ if (timeout < 0) {
+ throw new IllegalArgumentException("Invalid value \"" + value + "\" for the system property "
+ + name + ": a timeout in milliseconds cannot be negative");
+ }
+ return timeout;
+ }
+
+ private static IllegalArgumentException invalidProperty(String name, String value, NumberFormatException cause) {
+ return new IllegalArgumentException("Invalid value \"" + value + "\" for the system property "
+ + name + ": expected a number", cause);
+ }
+
public int getPort() {
return port;
}
@@ -115,6 +173,37 @@
this.skipSet = skipSet;
}
+ /**
+ * Returns how long {@link EmbeddedOpenDJ#close()} retries the deletion of the temporary
+ * directory of the instance, in milliseconds.
+ * <p>
+ * The deletion has to be retried because a file cannot be deleted on Windows while a
+ * handle to it is still open, and the server threads release their handles shortly after
+ * the server has been stopped. Whatever is still locked when this expires is scheduled for
+ * deletion on JVM exit. Set it to {@code 0} to delete once and never wait.
+ *
+ * @return the deletion timeout in milliseconds
+ */
+ public long getDeleteTimeout() {
+ return deleteTimeout;
+ }
+
+ /**
+ * Sets how long {@link EmbeddedOpenDJ#close()} retries the deletion of the temporary
+ * directory of the instance.
+ *
+ * @param deleteTimeout
+ * the deletion timeout in milliseconds, {@code 0} to delete once and never wait
+ * @throws IllegalArgumentException
+ * If the timeout is negative.
+ */
+ public void setDeleteTimeout(long deleteTimeout) {
+ if (deleteTimeout < 0) {
+ throw new IllegalArgumentException("The delete timeout cannot be negative, but was " + deleteTimeout);
+ }
+ this.deleteTimeout = deleteTimeout;
+ }
+
@Override
public String toString() {
return "Config {" +
@@ -127,6 +216,7 @@
", ldifSchema='" + ldifSchema + '\'' +
", file='" + file + '\'' +
", skipSet=" + skipSet +
+ ", deleteTimeout=" + deleteTimeout +
'}';
}
}
--
Gitblit v1.10.0