An immutable retry setting for API operations: the maximum number of times a failing
+ * operation is retried, and the time to wait between attempts.
+ *
+ *
A policy with maxRetries set to 0 disables retries.
+ */
+public final class RetryPolicy {
+
+ private final int maxRetries;
+ private final int retryIntervalMilliseconds;
+
+ /**
+ * Creates a retry policy.
+ *
+ * @param maxRetries The number of times that a failing API operation will be retried
+ * @param retryIntervalMilliseconds The number of milliseconds to wait between retries
+ * @throws IllegalArgumentException If either value is negative
+ */
+ public RetryPolicy(final int maxRetries, final int retryIntervalMilliseconds) {
+ if (maxRetries < 0) {
+ throw new IllegalArgumentException("maxRetries must not be negative: " + maxRetries);
+ }
+ if (retryIntervalMilliseconds < 0) {
+ throw new IllegalArgumentException(
+ "retryIntervalMilliseconds must not be negative: " + retryIntervalMilliseconds);
+ }
+ this.maxRetries = maxRetries;
+ this.retryIntervalMilliseconds = retryIntervalMilliseconds;
+ }
+
+ /**
+ * @return The number of times that a failing API operation will be retried
+ */
+ public int getMaxRetries() {
+ return maxRetries;
+ }
+
+ /**
+ * @return The number of milliseconds to wait between retries
+ */
+ public int getRetryIntervalMilliseconds() {
+ return retryIntervalMilliseconds;
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof RetryPolicy)) {
+ return false;
+ }
+ final RetryPolicy other = (RetryPolicy) o;
+ return maxRetries == other.maxRetries
+ && retryIntervalMilliseconds == other.retryIntervalMilliseconds;
+ }
+
+ @Override
+ public int hashCode() {
+ return 31 * maxRetries + retryIntervalMilliseconds;
+ }
+
+ @Override
+ public String toString() {
+ return "RetryPolicy{maxRetries=" + maxRetries
+ + ", retryIntervalMilliseconds=" + retryIntervalMilliseconds + "}";
+ }
+}
diff --git a/src/test/java/io/github/jopenlibs/vault/RetryPolicyTests.java b/src/test/java/io/github/jopenlibs/vault/RetryPolicyTests.java
new file mode 100644
index 00000000..914d5f51
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/RetryPolicyTests.java
@@ -0,0 +1,53 @@
+package io.github.jopenlibs.vault;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThrows;
+
+public class RetryPolicyTests {
+
+ @Test
+ public void testValues() {
+ final var policy = new RetryPolicy(5, 1000);
+ assertEquals(5, policy.getMaxRetries());
+ assertEquals(1000, policy.getRetryIntervalMilliseconds());
+ }
+
+ @Test
+ public void testZeroValuesAllowed() {
+ final var policy = new RetryPolicy(0, 0);
+ assertEquals(0, policy.getMaxRetries());
+ assertEquals(0, policy.getRetryIntervalMilliseconds());
+ }
+
+ @Test
+ public void testNegativeMaxRetriesRejected() {
+ assertThrows(IllegalArgumentException.class, () -> new RetryPolicy(-1, 1000));
+ }
+
+ @Test
+ public void testNegativeIntervalRejected() {
+ assertThrows(IllegalArgumentException.class, () -> new RetryPolicy(5, -1));
+ }
+
+ @Test
+ public void testEqualsAndHashCode() {
+ final var policy = new RetryPolicy(5, 1000);
+ final var same = new RetryPolicy(5, 1000);
+ assertEquals(policy, policy);
+ assertEquals(policy, same);
+ assertEquals(policy.hashCode(), same.hashCode());
+ assertNotEquals(policy, new RetryPolicy(4, 1000));
+ assertNotEquals(policy, new RetryPolicy(5, 999));
+ assertNotEquals(policy, null);
+ assertNotEquals(policy, "RetryPolicy");
+ }
+
+ @Test
+ public void testToString() {
+ assertEquals("RetryPolicy{maxRetries=5, retryIntervalMilliseconds=1000}",
+ new RetryPolicy(5, 1000).toString());
+ }
+}
From 701db2c225b2e2ba16b32ccddfdaa73d09e31001 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 14 Oct 2026 23:53:09 +0200
Subject: [PATCH 02/18] Implement RetryPolicy in Logical operations
---
.../github/jopenlibs/vault/api/Logical.java | 4 +-
.../jopenlibs/vault/api/OperationsBase.java | 38 +++++++-
.../vault/api/OperationsBaseRetryTests.java | 92 +++++++++++++++++++
3 files changed, 131 insertions(+), 3 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Logical.java b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
index ca7c91e7..a6710e20 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
@@ -164,8 +164,8 @@ public LogicalResponse read(final String path, Boolean shouldRetry, final Intege
return new LogicalResponse(restResponse, attempt, logicalOperations.readV2);
},
- shouldRetry ? config.getMaxRetries() : 1,
- config.getRetryIntervalMilliseconds()
+ shouldRetry ? getMaxRetries() : 1,
+ getRetryIntervalMilliseconds()
);
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
index 5a0b9f66..7c5940ed 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/OperationsBase.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.rest.Rest;
@@ -14,12 +15,47 @@ public abstract class OperationsBase {
protected final VaultConfig config;
+ /**
+ * The retries for this instance, or null to use the retries currently set in
+ * {@link #config}.
+ */
+ protected final RetryPolicy retryPolicy;
+
protected OperationsBase(VaultConfig config) {
+ this(config, null);
+ }
+
+ /**
+ * @param config The configuration
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration, read on every
+ * call
+ */
+ protected OperationsBase(VaultConfig config, RetryPolicy retryPolicy) {
this.config = config;
+ this.retryPolicy = retryPolicy;
+ }
+
+ /**
+ * @return The maximum number of retries from the retry policy if set, otherwise the one
+ * currently set in the configuration
+ */
+ protected int getMaxRetries() {
+ return retryPolicy != null ? retryPolicy.getMaxRetries() : config.getMaxRetries();
+ }
+
+ /**
+ * @return The interval between retries from the retry policy if set, otherwise the one
+ * currently set in the configuration
+ */
+ protected int getRetryIntervalMilliseconds() {
+ return retryPolicy != null
+ ? retryPolicy.getRetryIntervalMilliseconds()
+ : config.getRetryIntervalMilliseconds();
}
protected T retry(final EndpointOperation op) throws VaultException {
- return retry(op, config.getMaxRetries(), config.getRetryIntervalMilliseconds());
+ return retry(op, getMaxRetries(), getRetryIntervalMilliseconds());
}
/**
diff --git a/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java b/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java
index e339f8a8..3dbb3a64 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/OperationsBaseRetryTests.java
@@ -1,5 +1,8 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
+import io.github.jopenlibs.vault.Vault;
+import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.rest.Rest;
import io.github.jopenlibs.vault.rest.RestException;
@@ -10,6 +13,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -107,6 +111,94 @@ public void testStopsRetryingWhenInterrupted() {
assertTrue(Thread.currentThread().isInterrupted());
}
+ @Test
+ public void testNullPolicyUsesConfigRetries() throws Exception {
+ final var config = configWithRetries(2, 1);
+ final var operations = new TestOperations(config);
+ assertNull(operations.retryPolicy);
+ assertEquals(2, operations.getMaxRetries());
+ assertEquals(1, operations.getRetryIntervalMilliseconds());
+ assertEquals(3, operations.countAttempts());
+ }
+
+ @Test
+ public void testNullPolicyReadsConfigOnEveryCall() throws Exception {
+ final var config = configWithRetries(1, 1);
+ final var operations = new TestOperations(config, null);
+ assertEquals(2, operations.countAttempts());
+
+ Vault.create(config).withRetries(3, 1);
+ assertEquals(3, operations.getMaxRetries());
+ assertEquals(4, operations.countAttempts());
+ }
+
+ @Test
+ public void testNullPolicyKeepsNegativeConfigValues() throws Exception {
+ final var config = configWithRetries(-1, -5);
+ final var operations = new TestOperations(config);
+ assertEquals(-1, operations.getMaxRetries());
+ assertEquals(-5, operations.getRetryIntervalMilliseconds());
+ assertEquals(1, operations.countAttempts());
+ }
+
+ @Test
+ public void testPolicyOverridesConfigRetries() throws Exception {
+ final var config = configWithRetries(5, 1000);
+ final var policy = new RetryPolicy(1, 2);
+ final var operations = new TestOperations(config, policy);
+ assertSame(policy, operations.retryPolicy);
+ assertEquals(1, operations.getMaxRetries());
+ assertEquals(2, operations.getRetryIntervalMilliseconds());
+ assertEquals(2, operations.countAttempts());
+
+ Vault.create(config).withRetries(3, 1);
+ assertEquals(2, operations.countAttempts());
+ assertEquals(3, config.getMaxRetries());
+ }
+
+ @Test
+ public void testZeroPolicyDisablesRetries() throws Exception {
+ final var operations = new TestOperations(configWithRetries(5, 1), new RetryPolicy(0, 0));
+ assertEquals(1, operations.countAttempts());
+ }
+
+ private static VaultConfig configWithRetries(final int maxRetries,
+ final int retryIntervalMilliseconds) throws VaultException {
+ final var config = new VaultConfig().address("http://127.0.0.1:8999")
+ .token("mock_token").engineVersion(1).build();
+ Vault.create(config).withRetries(maxRetries, retryIntervalMilliseconds);
+ return config;
+ }
+
+ private static final class TestOperations extends OperationsBase {
+
+ TestOperations(final VaultConfig config) {
+ super(config);
+ }
+
+ TestOperations(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
+ }
+
+ /**
+ * @return The number of attempts made for an operation that always fails with a transient
+ * error
+ */
+ int countAttempts() {
+ final var calls = new AtomicInteger();
+ try {
+ retry(attempt -> {
+ calls.incrementAndGet();
+ throw new VaultException("unavailable", 503);
+ });
+ fail("Expected VaultException");
+ } catch (VaultException e) {
+ assertEquals(503, e.getHttpStatusCode());
+ }
+ return calls.get();
+ }
+ }
+
private static RestException connectionRefused() {
try {
new Rest().url("http://127.0.0.1:1/").connectTimeoutSeconds(1).get();
From 6bb9a40de84ef3c475cff0dd43c38724dc380b88 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Wed, 14 Oct 2026 23:56:29 +0200
Subject: [PATCH 03/18] Implement RetryPolicy in all constructor classes
---
.../io/github/jopenlibs/vault/api/Auth.java | 29 +++--
.../io/github/jopenlibs/vault/api/Debug.java | 13 ++-
.../github/jopenlibs/vault/api/Logical.java | 13 ++-
.../vault/api/database/Database.java | 19 +++-
.../github/jopenlibs/vault/api/pki/Pki.java | 19 +++-
.../jopenlibs/vault/api/sys/Leases.java | 13 ++-
.../github/jopenlibs/vault/api/sys/Seal.java | 13 ++-
.../github/jopenlibs/vault/api/sys/Sys.java | 21 +++-
.../jopenlibs/vault/api/sys/Wrapping.java | 13 ++-
.../vault/api/sys/mounts/Mounts.java | 13 ++-
.../api/RetryPolicyPropagationTests.java | 105 ++++++++++++++++++
11 files changed, 249 insertions(+), 22 deletions(-)
create mode 100644 src/test/java/io/github/jopenlibs/vault/api/RetryPolicyPropagationTests.java
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Auth.java b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
index 42017d04..55747698 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Auth.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Auth.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.VaultImpl;
@@ -254,7 +255,17 @@ public String getEntityAlias() {
private String nameSpace;
public Auth(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Auth(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
@@ -1153,7 +1164,7 @@ public LookupResponse lookupSelf(final String tokenAuthMount) throws VaultExcept
*/
@Deprecated
public LogicalResponse lookupWrap() throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().lookupWrap(config.getToken(), false);
}
@@ -1163,7 +1174,7 @@ public LogicalResponse lookupWrap() throws VaultException {
*/
@Deprecated
public LogicalResponse lookupWrap(final char[] wrappedToken) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().lookupWrap(wrappedToken, true);
}
@@ -1174,7 +1185,7 @@ public LogicalResponse lookupWrap(final char[] wrappedToken) throws VaultExcepti
@Deprecated
public LogicalResponse lookupWrap(final char[] wrappedToken, boolean inBody)
throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().lookupWrap(wrappedToken, inBody);
}
@@ -1220,7 +1231,7 @@ public void revokeSelf(final String tokenAuthMount) throws VaultException {
*/
@Deprecated
public UnwrapResponse unwrap() throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().unwrap(config.getToken(), false);
}
@@ -1230,7 +1241,7 @@ public UnwrapResponse unwrap() throws VaultException {
*/
@Deprecated
public UnwrapResponse unwrap(final char[] wrappedToken) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().unwrap(wrappedToken, true);
}
@@ -1240,7 +1251,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken) throws VaultException {
*/
@Deprecated
public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().unwrap(wrappedToken, inBody);
}
@@ -1250,7 +1261,7 @@ public UnwrapResponse unwrap(final char[] wrappedToken, boolean inBody) throws V
*/
@Deprecated
public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().wrap(jsonObject, ttlInSec);
}
@@ -1260,7 +1271,7 @@ public WrapResponse wrap(final JsonObject jsonObject, int ttlInSec) throws Vault
*/
@Deprecated
public WrapResponse rewrap(final char[] wrappedToken) throws VaultException {
- final var sys = new Sys(this.config);
+ final var sys = new Sys(this.config, this.retryPolicy);
return sys.wrapping().rewrap(wrappedToken);
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Debug.java b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
index 52832f34..ffe4de32 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Debug.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Debug.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.response.HealthResponse;
@@ -21,7 +22,17 @@ public class Debug extends OperationsBase {
private String nameSpace;
public Debug(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Debug(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Logical.java b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
index a6710e20..9d3fbbcb 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.json.Json;
@@ -37,7 +38,17 @@ public class Logical extends OperationsBase {
public enum logicalOperations {authentication, deleteV1, deleteV2, destroy, listV1, listV2, listSubKeys, readV1, readV2, writeV1, writeV2, unDelete, mount}
public Logical(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Logical(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
index b58fee17..4f51a72d 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/database/Database.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.database;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -49,7 +50,23 @@ public Database(final VaultConfig config) {
* /v1/ prefix (e.g. "root-ca")
*/
public Database(final VaultConfig config, final String mountPath) {
- super(config);
+ this(config, mountPath, null);
+ }
+
+ /**
+ * Constructor for use when the Database backend is mounted on some non-default custom path, with
+ * a retry policy.
+ *
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param mountPath The path on which your Vault Database backend is mounted, without the
+ * /v1/ prefix (e.g. "db123")
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Database(final VaultConfig config, final String mountPath,
+ final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.mountPath = mountPath;
this.nameSpace = this.config.getNameSpace();
diff --git a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
index 1a3148e8..9fb0463e 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/pki/Pki.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.pki;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -50,7 +51,23 @@ public Pki(final VaultConfig config) {
* /v1/ prefix (e.g. "root-ca")
*/
public Pki(final VaultConfig config, final String mountPath) {
- super(config);
+ this(config, mountPath, null);
+ }
+
+ /**
+ * Constructor for use when the PKI backend is mounted on some non-default custom path, with
+ * a retry policy.
+ *
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param mountPath The path on which your Vault PKI backend is mounted, without the
+ * /v1/ prefix (e.g. "root-ca")
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Pki(final VaultConfig config, final String mountPath,
+ final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.mountPath = mountPath;
this.nameSpace = this.config.getNameSpace();
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
index 10938922..8e06d2dd 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Leases.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -23,7 +24,17 @@ public class Leases extends OperationsBase {
private String nameSpace;
public Leases(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Leases(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
index d4bd8e02..976305da 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Seal.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -23,7 +24,17 @@ public class Seal extends OperationsBase {
private String nameSpace;
public Seal(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Seal(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
index 98011247..f5d3dc53 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Sys.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.Vault;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -19,7 +20,17 @@ public class Sys extends OperationsBase {
private String nameSpace;
public Sys(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Sys(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
@@ -35,7 +46,7 @@ public Sys withNameSpace(final String nameSpace) {
* @return The implementing class for wrapping operations
*/
public Wrapping wrapping() {
- return new Wrapping(this.config);
+ return new Wrapping(this.config, this.retryPolicy);
}
/**
@@ -44,7 +55,7 @@ public Wrapping wrapping() {
* @return The implementing class for Vault's seal operations (e.g. seal, unseal, sealStatus).
*/
public Seal seal() {
- return new Seal(this.config);
+ return new Seal(this.config, this.retryPolicy);
}
/**
@@ -54,7 +65,7 @@ public Seal seal() {
* @return the implementing class for Vault's sys mounts operations
*/
public Mounts mounts() {
- return new Mounts(this.config);
+ return new Mounts(this.config, this.retryPolicy);
}
/**
@@ -64,6 +75,6 @@ public Mounts mounts() {
* @return The implementing class for Vault's lease operations
*/
public Leases leases() {
- return new Leases(this.config);
+ return new Leases(this.config, this.retryPolicy);
}
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
index b8823ff9..bb88489c 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/Wrapping.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.Logical;
@@ -27,7 +28,17 @@ public class Wrapping extends OperationsBase {
private String nameSpace;
public Wrapping(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Wrapping(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
this.nameSpace = this.config.getNameSpace();
}
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
index 42b655bd..fa2862b6 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/Mounts.java
@@ -1,5 +1,6 @@
package io.github.jopenlibs.vault.api.sys.mounts;
+import io.github.jopenlibs.vault.RetryPolicy;
import io.github.jopenlibs.vault.VaultConfig;
import io.github.jopenlibs.vault.VaultException;
import io.github.jopenlibs.vault.api.OperationsBase;
@@ -17,7 +18,17 @@
public class Mounts extends OperationsBase {
public Mounts(final VaultConfig config) {
- super(config);
+ this(config, null);
+ }
+
+ /**
+ * @param config A container for the configuration settings needed to initialize a
+ * Vault driver instance
+ * @param retryPolicy The retries for this instance, overriding those set in the configuration,
+ * or null to use the retries currently set in the configuration
+ */
+ public Mounts(final VaultConfig config, final RetryPolicy retryPolicy) {
+ super(config, retryPolicy);
}
/**
diff --git a/src/test/java/io/github/jopenlibs/vault/api/RetryPolicyPropagationTests.java b/src/test/java/io/github/jopenlibs/vault/api/RetryPolicyPropagationTests.java
new file mode 100644
index 00000000..e621cc03
--- /dev/null
+++ b/src/test/java/io/github/jopenlibs/vault/api/RetryPolicyPropagationTests.java
@@ -0,0 +1,105 @@
+package io.github.jopenlibs.vault.api;
+
+import io.github.jopenlibs.vault.RetryPolicy;
+import io.github.jopenlibs.vault.VaultConfig;
+import io.github.jopenlibs.vault.VaultException;
+import io.github.jopenlibs.vault.VaultTestUtils;
+import io.github.jopenlibs.vault.api.database.Database;
+import io.github.jopenlibs.vault.api.pki.Pki;
+import io.github.jopenlibs.vault.api.sys.Leases;
+import io.github.jopenlibs.vault.api.sys.Seal;
+import io.github.jopenlibs.vault.api.sys.Sys;
+import io.github.jopenlibs.vault.api.sys.Wrapping;
+import io.github.jopenlibs.vault.api.sys.mounts.Mounts;
+import io.github.jopenlibs.vault.mock.RetriesMockVault;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+/**
+ * Tests that the operation classes keep the {@link RetryPolicy} they are given, and pass it to the
+ * operation classes they create.
+ */
+public class RetryPolicyPropagationTests {
+
+ private static final RetryPolicy POLICY = new RetryPolicy(3, 10);
+
+ @Test
+ public void testConstructorsKeepPolicy() throws VaultException {
+ final var config = config();
+ assertSame(POLICY, new Logical(config, POLICY).retryPolicy);
+ assertSame(POLICY, new Auth(config, POLICY).retryPolicy);
+ assertSame(POLICY, new Debug(config, POLICY).retryPolicy);
+ assertSame(POLICY, new Sys(config, POLICY).retryPolicy);
+ assertSame(POLICY, new Leases(config, POLICY).retryPolicy);
+ assertSame(POLICY, new Seal(config, POLICY).retryPolicy);
+ assertSame(POLICY, new Wrapping(config, POLICY).retryPolicy);
+ assertSame(POLICY, new Mounts(config, POLICY).retryPolicy);
+ assertSame(POLICY, new Pki(config, "pki", POLICY).retryPolicy);
+ assertSame(POLICY, new Database(config, "database", POLICY).retryPolicy);
+ }
+
+ @Test
+ public void testConfigOnlyConstructorsHaveNoPolicy() throws VaultException {
+ final var config = config();
+ assertNull(new Logical(config).retryPolicy);
+ assertNull(new Auth(config).retryPolicy);
+ assertNull(new Debug(config).retryPolicy);
+ assertNull(new Sys(config).retryPolicy);
+ assertNull(new Leases(config).retryPolicy);
+ assertNull(new Seal(config).retryPolicy);
+ assertNull(new Wrapping(config).retryPolicy);
+ assertNull(new Mounts(config).retryPolicy);
+ assertNull(new Pki(config).retryPolicy);
+ assertNull(new Pki(config, "root-ca").retryPolicy);
+ assertNull(new Database(config).retryPolicy);
+ assertNull(new Database(config, "db123").retryPolicy);
+ }
+
+ @Test
+ public void testPkiAndDatabaseKeepMountPathConstructorWithNull() throws VaultException {
+ final var config = config();
+ // Must still compile: a (VaultConfig, RetryPolicy) overload would make these ambiguous
+ assertNull(new Pki(config, null).retryPolicy);
+ assertNull(new Database(config, null).retryPolicy);
+ }
+
+ @Test
+ public void testSysPassesPolicy() throws VaultException {
+ final var sys = new Sys(config(), POLICY);
+ assertSame(POLICY, sys.wrapping().retryPolicy);
+ assertSame(POLICY, sys.seal().retryPolicy);
+ assertSame(POLICY, sys.mounts().retryPolicy);
+ assertSame(POLICY, sys.leases().retryPolicy);
+ }
+
+ @Test
+ public void testSysWithoutPolicyPassesNull() throws VaultException {
+ final var sys = new Sys(config());
+ assertNull(sys.wrapping().retryPolicy);
+ assertNull(sys.seal().retryPolicy);
+ assertNull(sys.mounts().retryPolicy);
+ assertNull(sys.leases().retryPolicy);
+ }
+
+ @Test
+ public void testAuthPassesPolicyToSys() throws Exception {
+ // The config allows no retries, so the call only succeeds if Auth passes its policy on
+ final var server = VaultTestUtils.initHttpMockVault(
+ new RetriesMockVault(2, 200, "{\"data\":{\"token\":\"mock\"}}"));
+ server.start();
+ try {
+ final var response = new Auth(config(), POLICY).lookupWrap();
+ assertEquals(2, response.getRetries());
+ } finally {
+ VaultTestUtils.shutdownMockVault(server);
+ }
+ }
+
+ private static VaultConfig config() throws VaultException {
+ return new VaultConfig().address("http://127.0.0.1:8999").token("mock_token")
+ .engineVersion(1).build();
+ }
+}
From b424b9b9314a651f47b3e305fec33a93e8055d6c Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 15 Oct 2026 00:01:49 +0200
Subject: [PATCH 04/18] Added new retrying() method in interface
---
.../github/jopenlibs/vault/RetryPolicy.java | 2 +
.../java/io/github/jopenlibs/vault/Vault.java | 31 ++++
.../io/github/jopenlibs/vault/VaultImpl.java | 59 ++++++--
.../io/github/jopenlibs/vault/RetryTests.java | 139 ++++++++++++++++++
4 files changed, 220 insertions(+), 11 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java b/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java
index 607dc810..06d9b09c 100644
--- a/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java
+++ b/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java
@@ -5,6 +5,8 @@
* operation is retried, and the time to wait between attempts.
*
*
A policy with maxRetries set to 0 disables retries.
+ *
+ * @see Vault#retrying(int, int)
*/
public final class RetryPolicy {
diff --git a/src/main/java/io/github/jopenlibs/vault/Vault.java b/src/main/java/io/github/jopenlibs/vault/Vault.java
index dffb117f..89e15a7d 100644
--- a/src/main/java/io/github/jopenlibs/vault/Vault.java
+++ b/src/main/java/io/github/jopenlibs/vault/Vault.java
@@ -61,6 +61,37 @@ static Vault create(final VaultConfig vaultConfig, final Boolean useSecretsEngin
*/
Vault withRetries(final int maxRetries, final int retryIntervalMilliseconds);
+ /**
+ *
Returns a new Vault whose API operations are retried with the given rules,
+ * overriding the retries set in the configuration. This instance, and its configuration, are
+ * left unchanged:
The returned instance shares the configuration of this one, so later changes to it (e.g.
+ * a new token) apply to both.
+ *
+ *
The default implementation throws UnsupportedOperationException, so that
+ * existing implementations of this interface keep compiling.
+ *
+ * @param maxRetries The number of times that API operations will be retried when a failure
+ * occurs
+ * @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
+ * between retries
+ * @return A new Vault using the given retries
+ * @throws IllegalArgumentException If either value is negative
+ * @throws UnsupportedOperationException If this implementation does not support it
+ * @see RetryPolicy
+ */
+ default Vault retrying(final int maxRetries, final int retryIntervalMilliseconds) {
+ throw new UnsupportedOperationException(
+ getClass().getName() + " does not support retrying()");
+ }
+
/**
* Returns the implementing class for Vault's core/logical operations (e.g. read, write).
*
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index 04b81768..daa15a7d 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -58,6 +58,11 @@
public class VaultImpl implements Vault {
private final VaultConfig vaultConfig;
+ /**
+ * The retries for the operations of this instance, or null to use the retries
+ * currently set in {@link #vaultConfig}.
+ */
+ private final RetryPolicy retryPolicy;
private final Logger logger = Logger.getLogger(VaultImpl.class.getCanonicalName());
/**
@@ -69,6 +74,7 @@ public class VaultImpl implements Vault {
*/
public VaultImpl(final VaultConfig vaultConfig) {
this.vaultConfig = vaultConfig;
+ this.retryPolicy = null;
logNameSpace();
if (this.vaultConfig.getSecretsEnginePathMap().isEmpty()
&& this.vaultConfig.getGlobalEngineVersion() == null) {
@@ -95,6 +101,7 @@ public VaultImpl(final VaultConfig vaultConfig, final Integer engineVersion) {
}
vaultConfig.setEngineVersion(engineVersion);
this.vaultConfig = vaultConfig;
+ this.retryPolicy = null;
logNameSpace();
}
@@ -115,6 +122,7 @@ public VaultImpl(final VaultConfig vaultConfig, final Boolean useSecretsEnginePa
final Integer globalFallbackVersion)
throws VaultException {
this.vaultConfig = vaultConfig;
+ this.retryPolicy = null;
logNameSpace();
this.vaultConfig.setEngineVersion(globalFallbackVersion);
if (Boolean.TRUE.equals(useSecretsEnginePathMap) && this.vaultConfig.getSecretsEnginePathMap().isEmpty()) {
@@ -139,6 +147,15 @@ public VaultImpl(final VaultConfig vaultConfig, final Boolean useSecretsEnginePa
}
}
+ /**
+ * Used by {@link #retrying(int, int)}: shares the configuration of an existing instance, which
+ * has already been set up, so the engine version setup is not run again.
+ */
+ private VaultImpl(final VaultConfig vaultConfig, final RetryPolicy retryPolicy) {
+ this.vaultConfig = vaultConfig;
+ this.retryPolicy = retryPolicy;
+ }
+
private void logNameSpace() {
final String nameSpace = this.vaultConfig.getNameSpace();
if (nameSpace != null && !nameSpace.isEmpty()) {
@@ -165,6 +182,26 @@ public VaultImpl withRetries(final int maxRetries, final int retryIntervalMillis
return this;
}
+ /**
+ *
Returns a new VaultImpl whose API operations are retried with the given
+ * rules, overriding the retries set in the configuration. This instance, and its configuration,
+ * are left unchanged.
+ *
+ *
The returned instance shares the configuration of this one, so later changes to it (e.g.
+ * a new token) apply to both.
+ *
+ * @param maxRetries The number of times that API operations will be retried when a failure
+ * occurs
+ * @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
+ * between retries
+ * @return A new VaultImpl using the given retries
+ * @throws IllegalArgumentException If either value is negative
+ */
+ @Override
+ public VaultImpl retrying(final int maxRetries, final int retryIntervalMilliseconds) {
+ return new VaultImpl(vaultConfig, new RetryPolicy(maxRetries, retryIntervalMilliseconds));
+ }
+
/**
* Returns the implementing class for Vault's core/logical operations (e.g. read, write).
*
@@ -172,7 +209,7 @@ public VaultImpl withRetries(final int maxRetries, final int retryIntervalMillis
*/
@Override
public Logical logical() {
- return new Logical(vaultConfig);
+ return new Logical(vaultConfig, retryPolicy);
}
/**
@@ -183,7 +220,7 @@ public Logical logical() {
*/
@Override
public Auth auth() {
- return new Auth(vaultConfig);
+ return new Auth(vaultConfig, retryPolicy);
}
/**
@@ -194,7 +231,7 @@ public Auth auth() {
*/
@Override
public Sys sys() {
- return new Sys(vaultConfig);
+ return new Sys(vaultConfig, retryPolicy);
}
/**
@@ -205,7 +242,7 @@ public Sys sys() {
*/
@Override
public Pki pki() {
- return new Pki(vaultConfig);
+ return new Pki(vaultConfig, "pki", retryPolicy);
}
/**
@@ -232,17 +269,17 @@ public Pki pki() {
*/
@Override
public Pki pki(final String mountPath) {
- return new Pki(vaultConfig, mountPath);
+ return new Pki(vaultConfig, mountPath, retryPolicy);
}
@Override
public Database database() {
- return new Database(vaultConfig);
+ return new Database(vaultConfig, "database", retryPolicy);
}
@Override
public Database database(final String mountPath) {
- return new Database(vaultConfig, mountPath);
+ return new Database(vaultConfig, mountPath, retryPolicy);
}
/**
@@ -255,7 +292,7 @@ public Database database(final String mountPath) {
@Override
@Deprecated
public Leases leases() {
- return new Leases(vaultConfig);
+ return new Leases(vaultConfig, retryPolicy);
}
/**
@@ -265,7 +302,7 @@ public Leases leases() {
*/
@Override
public Debug debug() {
- return new Debug(vaultConfig);
+ return new Debug(vaultConfig, retryPolicy);
}
/**
@@ -279,7 +316,7 @@ public Debug debug() {
@Override
@Deprecated
public Mounts mounts() {
- return new Mounts(vaultConfig);
+ return new Mounts(vaultConfig, retryPolicy);
}
/**
@@ -292,7 +329,7 @@ public Mounts mounts() {
@Override
@Deprecated
public Seal seal() {
- return new Seal(vaultConfig);
+ return new Seal(vaultConfig, retryPolicy);
}
/**
diff --git a/src/test/java/io/github/jopenlibs/vault/RetryTests.java b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
index 2b021f9b..e98e5941 100644
--- a/src/test/java/io/github/jopenlibs/vault/RetryTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
@@ -1,11 +1,18 @@
package io.github.jopenlibs.vault;
+import io.github.jopenlibs.vault.api.OperationsBase;
import io.github.jopenlibs.vault.mock.RetriesMockVault;
import io.github.jopenlibs.vault.response.LogicalResponse;
+import java.util.List;
import java.util.Map;
import org.junit.Test;
+import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
/**
*
Unit tests for the Vault driver, having no dependency on an actual Vault server instance
@@ -117,4 +124,136 @@ public void testNoRetries_Write_400() throws Exception {
VaultTestUtils.shutdownMockVault(server);
}
+ private static final String READ_RESPONSE =
+ "{\"lease_id\":\"12345\",\"renewable\":false,\"lease_duration\":10000,\"data\":{\"value\":\"mock\"}}";
+
+ @Test
+ public void testRetrying_Read() throws Exception {
+ final var server = VaultTestUtils.initHttpMockVault(
+ new RetriesMockVault(5, 200, READ_RESPONSE));
+ server.start();
+ try {
+ final var vault = Vault.create(mockConfig());
+ final LogicalResponse response = vault.retrying(5, 10).logical().read("secret/hello");
+ assertEquals(5, response.getRetries());
+ assertEquals("mock", response.getData().get("value"));
+ } finally {
+ VaultTestUtils.shutdownMockVault(server);
+ }
+ }
+
+ @Test
+ public void testRetrying_LeavesOriginalUnchanged() throws Exception {
+ final var server = VaultTestUtils.initHttpMockVault(
+ new RetriesMockVault(1, 200, READ_RESPONSE));
+ server.start();
+ try {
+ final var config = mockConfig();
+ final var vault = Vault.create(config);
+ assertNotSame(vault, vault.retrying(5, 10));
+ assertEquals(0, config.getMaxRetries());
+ assertEquals(0, config.getRetryIntervalMilliseconds());
+
+ final var e = assertThrows(VaultException.class,
+ () -> vault.logical().read("secret/hello"));
+ assertEquals(500, e.getHttpStatusCode());
+ } finally {
+ VaultTestUtils.shutdownMockVault(server);
+ }
+ }
+
+ @Test
+ public void testRetrying_OverridesConfigRetries() throws Exception {
+ final var server = VaultTestUtils.initHttpMockVault(
+ new RetriesMockVault(3, 200, READ_RESPONSE));
+ server.start();
+ try {
+ final var vault = Vault.create(mockConfig());
+ vault.withRetries(5, 10);
+ // 1 retry is fewer than the 3 failures, while the config retries would be enough
+ final var e = assertThrows(VaultException.class,
+ () -> vault.retrying(1, 10).logical().read("secret/hello"));
+ assertEquals(500, e.getHttpStatusCode());
+ } finally {
+ VaultTestUtils.shutdownMockVault(server);
+ }
+ }
+
+ @Test
+ public void testRetrying_SysLeasesGetsPolicy() throws Exception {
+ final var server = VaultTestUtils.initHttpMockVault(new RetriesMockVault(2, 204, null));
+ server.start();
+ try {
+ final var vault = Vault.create(mockConfig());
+ final var response = vault.retrying(2, 10).sys().leases().revoke("lease-id");
+ assertEquals(2, response.getRetries());
+ } finally {
+ VaultTestUtils.shutdownMockVault(server);
+ }
+ }
+
+ @Test
+ public void testRetrying_SharesConfig() throws Exception {
+ final var config = mockConfig();
+ final var retrying = Vault.create(config).retrying(5, 10);
+ assertSame(config, baseFieldOf(retrying.auth(), "config"));
+ config.token("new_token");
+ assertArrayEquals("new_token".toCharArray(),
+ ((VaultConfig) baseFieldOf(retrying.logical(), "config")).getToken());
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testRetrying_EveryOperationGetsPolicy() throws Exception {
+ final var vault = Vault.create(mockConfig());
+ final var policy = new RetryPolicy(5, 10);
+ final var retrying = vault.retrying(5, 10);
+ for (final OperationsBase operations : List.of(retrying.logical(), retrying.auth(),
+ retrying.sys(), retrying.pki(), retrying.pki("root-ca"), retrying.database(),
+ retrying.database("db123"), retrying.leases(), retrying.debug(),
+ retrying.mounts(), retrying.seal())) {
+ assertEquals(operations.getClass().getName(), policy, retryPolicyOf(operations));
+ }
+ for (final OperationsBase operations : List.of(vault.logical(), vault.pki(),
+ vault.database())) {
+ assertNull(operations.getClass().getName(), retryPolicyOf(operations));
+ }
+ }
+
+ @Test
+ public void testRetrying_PkiAndDatabaseUseDefaultMountPaths() throws Exception {
+ final var retrying = Vault.create(mockConfig()).retrying(5, 10);
+ assertEquals("pki", fieldOf(retrying.pki(), "mountPath"));
+ assertEquals("database", fieldOf(retrying.database(), "mountPath"));
+ }
+
+ @Test
+ public void testRetrying_RejectsNegativeValues() throws Exception {
+ final var vault = Vault.create(mockConfig());
+ assertThrows(IllegalArgumentException.class, () -> vault.retrying(-1, 10));
+ assertThrows(IllegalArgumentException.class, () -> vault.retrying(5, -1));
+ }
+
+ private static VaultConfig mockConfig() throws VaultException {
+ return new VaultConfig().address("http://127.0.0.1:8999")
+ .token("mock_token").engineVersion(1).build();
+ }
+
+ private static Object retryPolicyOf(final OperationsBase operations) throws Exception {
+ return baseFieldOf(operations, "retryPolicy");
+ }
+
+ private static Object baseFieldOf(final OperationsBase operations, final String name)
+ throws Exception {
+ final var field = OperationsBase.class.getDeclaredField(name);
+ field.setAccessible(true);
+ return field.get(operations);
+ }
+
+ private static Object fieldOf(final Object target, final String name) throws Exception {
+ final var field = target.getClass().getDeclaredField(name);
+ field.setAccessible(true);
+ return field.get(target);
+ }
+
}
From 63b02ad381291665c18a77575800a7aa3e0effa5 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 15 Oct 2026 00:06:02 +0200
Subject: [PATCH 05/18] Added builder method for retries
---
.../github/jopenlibs/vault/RetryPolicy.java | 1 +
.../java/io/github/jopenlibs/vault/Vault.java | 1 +
.../github/jopenlibs/vault/VaultConfig.java | 26 +++++++++
.../io/github/jopenlibs/vault/RetryTests.java | 56 +++++++++++++++++++
4 files changed, 84 insertions(+)
diff --git a/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java b/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java
index 06d9b09c..9682bc61 100644
--- a/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java
+++ b/src/main/java/io/github/jopenlibs/vault/RetryPolicy.java
@@ -7,6 +7,7 @@
*
A policy with maxRetries set to 0 disables retries.
*
* @see Vault#retrying(int, int)
+ * @see VaultConfig#retries(int, int)
*/
public final class RetryPolicy {
diff --git a/src/main/java/io/github/jopenlibs/vault/Vault.java b/src/main/java/io/github/jopenlibs/vault/Vault.java
index 89e15a7d..a4dcfae9 100644
--- a/src/main/java/io/github/jopenlibs/vault/Vault.java
+++ b/src/main/java/io/github/jopenlibs/vault/Vault.java
@@ -86,6 +86,7 @@ static Vault create(final VaultConfig vaultConfig, final Boolean useSecretsEngin
* @throws IllegalArgumentException If either value is negative
* @throws UnsupportedOperationException If this implementation does not support it
* @see RetryPolicy
+ * @see VaultConfig#retries(int, int)
*/
default Vault retrying(final int maxRetries, final int retryIntervalMilliseconds) {
throw new UnsupportedOperationException(
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index c480c487..add1bc5c 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -319,6 +319,32 @@ public VaultConfig httpClient(HttpClient httpClient) {
return this;
}
+ /**
+ *
Sets the retry rules for every API operation using this configuration: the maximum number
+ * of times that a failing operation will be retried, and the time to wait between attempts.
+ * By default, operations are not retried.
+ *
+ *
To retry only some calls, use Vault.retrying() instead, which overrides
+ * these values.
+ *
+ * @param maxRetries The number of times that API operations will be retried when a failure
+ * occurs
+ * @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
+ * between retries
+ * @return This object, with maxRetries and retryIntervalMilliseconds populated, ready for
+ * additional builder-pattern method calls or else finalization with the build() method
+ * @throws IllegalArgumentException If either value is negative, in which case the previous
+ * values are kept
+ * @see Vault#retrying(int, int)
+ * @see RetryPolicy
+ */
+ public VaultConfig retries(final int maxRetries, final int retryIntervalMilliseconds) {
+ final var policy = new RetryPolicy(maxRetries, retryIntervalMilliseconds);
+ this.maxRetries = policy.getMaxRetries();
+ this.retryIntervalMilliseconds = policy.getRetryIntervalMilliseconds();
+ return this;
+ }
+
/**
*
Sets the maximum number of times that an API operation will retry upon failure.
*
diff --git a/src/test/java/io/github/jopenlibs/vault/RetryTests.java b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
index e98e5941..6b657998 100644
--- a/src/test/java/io/github/jopenlibs/vault/RetryTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
@@ -234,6 +234,62 @@ public void testRetrying_RejectsNegativeValues() throws Exception {
assertThrows(IllegalArgumentException.class, () -> vault.retrying(5, -1));
}
+ @Test
+ public void testConfigRetries_SetsValues() throws Exception {
+ final var config = new VaultConfig();
+ assertSame(config, config.retries(5, 1000));
+ assertEquals(5, config.getMaxRetries());
+ assertEquals(1000, config.getRetryIntervalMilliseconds());
+ }
+
+ @Test
+ public void testConfigRetries_AppliesToEveryCall() throws Exception {
+ final var vault = Vault.create(new VaultConfig().address("http://127.0.0.1:8999")
+ .token("mock_token").engineVersion(1).retries(2, 10).build());
+
+ final var readServer = VaultTestUtils.initHttpMockVault(
+ new RetriesMockVault(2, 200, READ_RESPONSE));
+ readServer.start();
+ try {
+ assertEquals(2, vault.logical().read("secret/hello").getRetries());
+ } finally {
+ VaultTestUtils.shutdownMockVault(readServer);
+ }
+
+ final var revokeServer = VaultTestUtils.initHttpMockVault(
+ new RetriesMockVault(2, 204, null));
+ revokeServer.start();
+ try {
+ assertEquals(2, vault.sys().leases().revoke("lease-id").getRetries());
+ } finally {
+ VaultTestUtils.shutdownMockVault(revokeServer);
+ }
+ }
+
+ @Test
+ public void testConfigRetries_RejectsNegativeValues() throws Exception {
+ final var config = new VaultConfig().retries(5, 1000);
+ assertThrows(IllegalArgumentException.class, () -> config.retries(-1, 10));
+ assertThrows(IllegalArgumentException.class, () -> config.retries(3, -1));
+ assertEquals(5, config.getMaxRetries());
+ assertEquals(1000, config.getRetryIntervalMilliseconds());
+ }
+
+ @Test
+ public void testConfigRetries_OverriddenByRetrying() throws Exception {
+ final var server = VaultTestUtils.initHttpMockVault(
+ new RetriesMockVault(3, 200, READ_RESPONSE));
+ server.start();
+ try {
+ final var vault = Vault.create(mockConfig().retries(5, 10));
+ final var e = assertThrows(VaultException.class,
+ () -> vault.retrying(1, 10).logical().read("secret/hello"));
+ assertEquals(500, e.getHttpStatusCode());
+ } finally {
+ VaultTestUtils.shutdownMockVault(server);
+ }
+ }
+
private static VaultConfig mockConfig() throws VaultException {
return new VaultConfig().address("http://127.0.0.1:8999")
.token("mock_token").engineVersion(1).build();
From 3771e871e09b277d8c2bd712d4e4bf81deb65b03 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 15 Oct 2026 00:10:08 +0200
Subject: [PATCH 06/18] Deprecated withRetries() method
---
.../java/io/github/jopenlibs/vault/Vault.java | 15 ++++++++++---
.../github/jopenlibs/vault/VaultConfig.java | 8 +++----
.../io/github/jopenlibs/vault/VaultImpl.java | 17 +++++++++++----
.../io/github/jopenlibs/vault/RetryTests.java | 21 +++++++++++++++++++
4 files changed, 50 insertions(+), 11 deletions(-)
diff --git a/src/main/java/io/github/jopenlibs/vault/Vault.java b/src/main/java/io/github/jopenlibs/vault/Vault.java
index a4dcfae9..e696d542 100644
--- a/src/main/java/io/github/jopenlibs/vault/Vault.java
+++ b/src/main/java/io/github/jopenlibs/vault/Vault.java
@@ -50,15 +50,24 @@ static Vault create(final VaultConfig vaultConfig, final Boolean useSecretsEngin
}
/**
- * This method is chained ahead of endpoints (e.g. logical(), auth(),
- * etc... to specify retry rules for any API operations invoked on that endpoint.
+ *
Sets the retry rules for API operations, by writing them to the configuration of this
+ * instance.
+ *
+ *
Despite being chained ahead of endpoints (e.g. logical(),
+ * auth()), the retries are not limited to that call: they apply to every later
+ * call on this instance, and on every other Vault sharing the same configuration.
+ * A Vault returned by {@link #retrying(int, int)} ignores them.
*
* @param maxRetries The number of times that API operations will be retried when a failure
* occurs
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
* between retries
- * @return This object, with maxRetries and retryIntervalMilliseconds populated
+ * @return This object, with maxRetries and retryIntervalMilliseconds populated in its
+ * configuration
+ * @deprecated To retry only some calls, use {@link #retrying(int, int)}, which returns a new
+ * instance. To retry every call, use {@link VaultConfig#retries(int, int)}.
*/
+ @Deprecated
Vault withRetries(final int maxRetries, final int retryIntervalMilliseconds);
/**
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
index add1bc5c..190337d3 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultConfig.java
@@ -349,8 +349,8 @@ public VaultConfig retries(final int maxRetries, final int retryIntervalMillisec
*
Sets the maximum number of times that an API operation will retry upon failure.
*
*
This method is not meant to be called from application-level code outside of this package
- * (hence the protected access level. It is meant to be invoked via
- * Vault.withRetries() in a builder pattern DSL-style.
+ * (hence the package-private access level). It is used by the deprecated
+ * Vault.withRetries(): use {@link #retries(int, int)} instead.
*
* @param maxRetries The number of times that API operations will be retried when a failure
* occurs.
@@ -364,8 +364,8 @@ void setMaxRetries(final int maxRetries) {
* attempts for a failing API operation.
*
*
This method is not meant to be called from application-level code outside of this package
- * (hence the protected access level. It is meant to be invoked via
- * Vault.withRetries() in a builder pattern DSL-style.
+ * (hence the package-private access level). It is used by the deprecated
+ * Vault.withRetries(): use {@link #retries(int, int)} instead.
*
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
* between retries.
diff --git a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
index daa15a7d..64bce224 100644
--- a/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
+++ b/src/main/java/io/github/jopenlibs/vault/VaultImpl.java
@@ -43,7 +43,7 @@
* secrets.put("other_value", "You can store multiple name/value pairs under a given key");
*
* final LogicalResponse writeResponse = vault
- * .withRetries(5, 1000) // optional
+ * .retrying(5, 1000) // optional
* .logical()
* .write("secret/hello", secrets);
*
@@ -166,16 +166,25 @@ private void logNameSpace() {
}
/**
- * This method is chained ahead of endpoints (e.g. logical(), auth(),
- * etc... to specify retry rules for any API operations invoked on that endpoint.
+ *
Sets the retry rules for API operations, by writing them to the configuration of this
+ * instance.
+ *
+ *
Despite being chained ahead of endpoints (e.g. logical(),
+ * auth()), the retries are not limited to that call: they apply to every later
+ * call on this instance, and on every other Vault sharing the same configuration.
+ * A Vault returned by {@link #retrying(int, int)} ignores them.
*
* @param maxRetries The number of times that API operations will be retried when a failure
* occurs
* @param retryIntervalMilliseconds The number of milliseconds that the driver will wait in
* between retries
- * @return This object, with maxRetries and retryIntervalMilliseconds populated
+ * @return This object, with maxRetries and retryIntervalMilliseconds populated in its
+ * configuration
+ * @deprecated To retry only some calls, use {@link #retrying(int, int)}, which returns a new
+ * instance. To retry every call, use {@link VaultConfig#retries(int, int)}.
*/
@Override
+ @Deprecated
public VaultImpl withRetries(final int maxRetries, final int retryIntervalMilliseconds) {
this.vaultConfig.setMaxRetries(maxRetries);
this.vaultConfig.setRetryIntervalMilliseconds(retryIntervalMilliseconds);
diff --git a/src/test/java/io/github/jopenlibs/vault/RetryTests.java b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
index 6b657998..cb47620d 100644
--- a/src/test/java/io/github/jopenlibs/vault/RetryTests.java
+++ b/src/test/java/io/github/jopenlibs/vault/RetryTests.java
@@ -290,6 +290,27 @@ public void testConfigRetries_OverriddenByRetrying() throws Exception {
}
}
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testWithRetries_Unchanged() throws Exception {
+ final var config = mockConfig();
+ final var vault = Vault.create(config);
+ final var other = Vault.create(config);
+ final var retrying = vault.retrying(1, 10);
+
+ assertSame(vault, vault.withRetries(5, 1000));
+ assertEquals(5, config.getMaxRetries());
+ assertEquals(1000, config.getRetryIntervalMilliseconds());
+ // Shared by every Vault using the config, but ignored by one returned by retrying()
+ assertNull(retryPolicyOf(other.logical()));
+ assertEquals(new RetryPolicy(1, 10), retryPolicyOf(retrying.logical()));
+
+ // Negative values are still written to the config, not validated
+ vault.withRetries(-1, -1);
+ assertEquals(-1, config.getMaxRetries());
+ assertEquals(-1, config.getRetryIntervalMilliseconds());
+ }
+
private static VaultConfig mockConfig() throws VaultException {
return new VaultConfig().address("http://127.0.0.1:8999")
.token("mock_token").engineVersion(1).build();
From 146faefe4d30fc9fecade076b7f490863b343500 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Thu, 15 Oct 2026 00:11:57 +0200
Subject: [PATCH 07/18] Updated README
---
README.md | 32 +++++++++++++++++++++++++++++---
1 file changed, 29 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 47971243..c0ff308e 100644
--- a/README.md
+++ b/README.md
@@ -326,16 +326,31 @@ Vault's HTTP API:
credentials).
* `debug()`: Health check endpoints.
-The driver DSL also allows you to specify retry logic, by chaining the `withRetries()` ahead of
-accessing the endpoint implementation:
+By default, API operations are not retried. To retry every call, set the retries in the
+configuration:
```
// Retry up to 5 times if failures occur, waiting 1000 milliseconds in between each retry attempt.
-final LogicalResponse response = vault.withRetries(5, 1000)
+final VaultConfig config = new VaultConfig()
+ .address("http://127.0.0.1:8200")
+ .token("3c9fd6be-7bc2-9d1f-6fb3-cd746c0fc4e8")
+ .retries(5, 1000)
+ .build();
+```
+
+To retry only some calls, chain `retrying()` ahead of accessing the endpoint implementation. It
+returns a new `Vault` sharing the same configuration, and leaves the original `vault` unchanged:
+
+```
+final LogicalResponse response = vault.retrying(5, 1000)
.logical()
.read("secret/hello");
```
+`withRetries()` is deprecated: despite being chained like `retrying()`, it changes the retries in the
+configuration, so they apply to every later call, and to every `Vault` sharing that configuration.
+Replace it with `retrying()` or `VaultConfig.retries()`.
+
Only transient failures are retried: I/O errors (e.g. connection refused or timed out) and HTTP 5xx,
408, 412 and 429 responses. Other 4xx responses (e.g. 400, 403, 404) fail immediately, since
retrying them would not help.
@@ -407,6 +422,17 @@ patches.
* Javadoc examples use the `Sys` methods instead of the deprecated ones. The `Wrapping` examples
are fixed: they used `new Vault(config)` instead of `Vault.create(config)`, and a misspelled
`lookupWarp()` method
+ * Retries can be limited to some calls [(issue #75)](https://github.com/jopenlibs/vault-java-driver/issues/75).
+ `Vault.retrying(maxRetries, retryIntervalMilliseconds)` returns a new `Vault` whose operations
+ use those retries, leaving the original instance and its configuration unchanged.
+ `VaultConfig.retries(maxRetries, retryIntervalMilliseconds)` sets retries for every call.
+ Both reject negative values with `IllegalArgumentException`. The operation classes (e.g.
+ `Logical`, `Sys`, `Pki`) have new constructors taking a `RetryPolicy`
+ * `Vault.withRetries()` is deprecated, with unchanged behaviour: it writes the retries to the
+ shared `VaultConfig`, so they apply to every later call and to every `Vault` sharing that
+ configuration, not only the chained call. Use `retrying()` or `VaultConfig.retries()` instead.
+ `Vault.retrying()` is a `default` method throwing `UnsupportedOperationException`, so custom
+ implementations of `Vault` keep compiling
* **6.2.3**: This release contains the following updates:
* Improved Java 11 compatibility
* Fixed integration tests with Vault 2.x
From 95ef4a54d755696809be53cfc9e186138e53bc46 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Fri, 16 Oct 2026 01:01:01 +0200
Subject: [PATCH 08/18] Updated test to adapt to latest version of Vault
---
.../java/io/github/jopenlibs/vault/api/MountsIT.java | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/src/test/java/io/github/jopenlibs/vault/api/MountsIT.java b/src/test/java/io/github/jopenlibs/vault/api/MountsIT.java
index 6c5e2388..ee96719a 100644
--- a/src/test/java/io/github/jopenlibs/vault/api/MountsIT.java
+++ b/src/test/java/io/github/jopenlibs/vault/api/MountsIT.java
@@ -20,6 +20,7 @@
import org.junit.rules.ExpectedException;
import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
/**
@@ -152,12 +153,13 @@ public void testRead() throws VaultException {
@Test
public void testReadExceptionNotFound() throws VaultException {
- expectedEx.expect(VaultException.class);
- expectedEx.expectMessage("cannot fetch sysview for path");
-
final Vault vault = container.getRootVault();
- vault.sys().mounts().read("pki-non-existing-path");
+ // Vault 2.2 and later answer "invalid request" instead of "cannot fetch sysview for path",
+ // so only the status code is checked
+ final VaultException e = assertThrows(VaultException.class,
+ () -> vault.sys().mounts().read("pki-non-existing-path"));
+ assertEquals(400, e.getHttpStatusCode());
}
@Test
From 34bb410b412626f1ad07d7d4435fa60398614b5a Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Fri, 16 Oct 2026 01:01:48 +0200
Subject: [PATCH 09/18] Added new version in CI
---
.github/workflows/ci.yml | 1 +
1 file changed, 1 insertion(+)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 39ec971f..71fdd2a5 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -31,6 +31,7 @@ jobs:
- 2.1.0
- 2.1.1
- 2.1.2
+ - 2.2.0
- latest
os:
- ubuntu-latest
From 9c314d57f5709e974968a7ce1c9f3533e9c900f7 Mon Sep 17 00:00:00 2001
From: Enrico Bianchi
Date: Fri, 16 Oct 2026 00:38:47 +0200
Subject: [PATCH 10/18] Updated vendored JSON library
---
.../github/jopenlibs/vault/api/Logical.java | 6 +-
.../vault/api/sys/mounts/MountPayload.java | 48 +-
.../io/github/jopenlibs/vault/json/Json.java | 537 ++---
.../jopenlibs/vault/json/JsonArray.java | 800 ++++----
.../jopenlibs/vault/json/JsonHandler.java | 263 +++
.../jopenlibs/vault/json/JsonLiteral.java | 112 +-
.../jopenlibs/vault/json/JsonNumber.java | 104 +-
.../jopenlibs/vault/json/JsonObject.java | 1692 ++++++++--------
.../jopenlibs/vault/json/JsonParser.java | 774 ++++---
.../jopenlibs/vault/json/JsonString.java | 72 +-
.../jopenlibs/vault/json/JsonValue.java | 674 ++++---
.../jopenlibs/vault/json/JsonWriter.java | 212 +-
.../github/jopenlibs/vault/json/Location.java | 76 +
.../jopenlibs/vault/json/ParseException.java | 85 +-
.../jopenlibs/vault/json/PrettyPrint.java | 204 +-
.../jopenlibs/vault/json/WriterConfig.java | 31 +-
.../jopenlibs/vault/json/WritingBuffer.java | 105 +-
.../vault/response/MountResponse.java | 34 +-
.../jopenlibs/vault/json/JsonArray_Test.java | 1048 +++++-----
.../vault/json/JsonLiteral_Test.java | 273 ++-
.../jopenlibs/vault/json/JsonNumber_Test.java | 289 +--
.../jopenlibs/vault/json/JsonObject_Test.java | 1771 +++++++++--------
.../jopenlibs/vault/json/JsonParser_Test.java | 1158 +++++++----
.../jopenlibs/vault/json/JsonString_Test.java | 177 +-
.../jopenlibs/vault/json/JsonValue_Test.java | 332 +--
.../jopenlibs/vault/json/JsonWriter_Test.java | 221 +-
.../jopenlibs/vault/json/Json_Test.java | 446 +++--
.../vault/json/ParseException_Test.java | 44 +-
.../vault/json/PrettyPrint_Test.java | 197 +-
.../github/jopenlibs/vault/json/TestUtil.java | 133 +-
.../vault/json/WritingBuffer_Test.java | 241 ++-
.../vault/json/test/mocking/Mocking_Test.java | 39 +-
32 files changed, 6822 insertions(+), 5376 deletions(-)
create mode 100644 src/main/java/io/github/jopenlibs/vault/json/JsonHandler.java
create mode 100644 src/main/java/io/github/jopenlibs/vault/json/Location.java
diff --git a/src/main/java/io/github/jopenlibs/vault/api/Logical.java b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
index 9d3fbbcb..9844a357 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/Logical.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/Logical.java
@@ -442,7 +442,7 @@ public LogicalResponse delete(final String path, final int[] versions) throws Va
return retry(attempt -> {
// Make an HTTP request to Vault
- final var versionsToDelete = new JsonObject().add("versions", versions);
+ final var versionsToDelete = new JsonObject().add("versions", Json.array(versions));
final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/" + adjustPathForVersionDelete(path,
config.getPrefixPathDepth()))
@@ -499,7 +499,7 @@ public LogicalResponse unDelete(final String path, final int[] versions) throws
return retry(attempt -> {
// Make an HTTP request to Vault
- final var versionsToUnDelete = new JsonObject().add("versions", versions);
+ final var versionsToUnDelete = new JsonObject().add("versions", Json.array(versions));
final var restResponse = getRest() //NOPMD
.url(config.getAddress() + "/v1/" + adjustPathForVersionUnDelete(path,
config.getPrefixPathDepth()))
@@ -543,7 +543,7 @@ public LogicalResponse destroy(final String path, final int[] versions) throws V
return retry(attempt -> {
// Make an HTTP request to Vault
- final var versionsToDestroy = new JsonObject().add("versions", versions);
+ final var versionsToDestroy = new JsonObject().add("versions", Json.array(versions));
final var restResponse = getRest()//NOPMD
.url(config.getAddress() + "/v1/" + adjustPathForVersionDestroy(path,
config.getPrefixPathDepth()))
diff --git a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountPayload.java b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountPayload.java
index 8888a9a9..9292c7b8 100644
--- a/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountPayload.java
+++ b/src/main/java/io/github/jopenlibs/vault/api/sys/mounts/MountPayload.java
@@ -2,6 +2,7 @@
import io.github.jopenlibs.vault.json.Json;
import io.github.jopenlibs.vault.json.JsonObject;
+import io.github.jopenlibs.vault.json.JsonValue;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@@ -101,12 +102,12 @@ public List getAuditNonHmacResponseKeys() {
public JsonObject toEnableJson(MountType type) {
final JsonObject jsonObject = Json.object();
- jsonObject.addIfNotNull("type", type.value());
- jsonObject.addIfNotNull("description", this.description);
- jsonObject.addIfNotNull("config", this.toConfigJson());
- jsonObject.addIfNotNull("plugin_name", this.pluginName);
- jsonObject.addIfNotNull("local", this.local);
- jsonObject.addIfNotNull("seal_wrap", this.sealWrap);
+ addIfNotNull(jsonObject, "type", type.value());
+ addIfNotNull(jsonObject, "description", this.description);
+ addIfNotNull(jsonObject, "config", this.toConfigJson());
+ addIfNotNull(jsonObject, "plugin_name", this.pluginName);
+ addIfNotNull(jsonObject, "local", this.local);
+ addIfNotNull(jsonObject, "seal_wrap", this.sealWrap);
return jsonObject;
}
@@ -115,22 +116,22 @@ public JsonObject toTuneJson() {
final JsonObject jsonObject = Json.object();
if (this.defaultLeaseTtl != null) {
- jsonObject.addIfNotNull("default_lease_ttl", this.defaultLeaseTtl.toString());
+ addIfNotNull(jsonObject, "default_lease_ttl", this.defaultLeaseTtl.toString());
}
if (this.maxLeaseTtl != null) {
- jsonObject.addIfNotNull("max_lease_ttl", this.maxLeaseTtl.toString());
+ addIfNotNull(jsonObject, "max_lease_ttl", this.maxLeaseTtl.toString());
}
- jsonObject.addIfNotNull("description", this.description);
+ addIfNotNull(jsonObject, "description", this.description);
if (this.auditNonHmacRequestKeys != null && !this.auditNonHmacRequestKeys.isEmpty()) {
- jsonObject.addIfNotNull("audit_non_hmac_request_keys",
+ addIfNotNull(jsonObject, "audit_non_hmac_request_keys",
String.join(",", this.auditNonHmacRequestKeys));
}
if (this.auditNonHmacResponseKeys != null && !this.auditNonHmacResponseKeys.isEmpty()) {
- jsonObject.addIfNotNull("audit_non_hmac_response_keys",
+ addIfNotNull(jsonObject, "audit_non_hmac_response_keys",
String.join(",", this.auditNonHmacResponseKeys));
}
@@ -140,8 +141,8 @@ public JsonObject toTuneJson() {
private JsonObject toConfigJson() {
final JsonObject jsonObject = toTuneJson();
- jsonObject.addIfNotNull("force_no_cache", this.forceNoCache);
- jsonObject.addIfNotNull("plugin_name", this.pluginName);
+ addIfNotNull(jsonObject, "force_no_cache", this.forceNoCache);
+ addIfNotNull(jsonObject, "plugin_name", this.pluginName);
if (jsonObject.isEmpty()) {
return null;
@@ -150,6 +151,27 @@ private JsonObject toConfigJson() {
return jsonObject;
}
+ private static void addIfNotNull(final JsonObject jsonObject, final String name,
+ final String value) {
+ if (value != null) {
+ jsonObject.add(name, value);
+ }
+ }
+
+ private static void addIfNotNull(final JsonObject jsonObject, final String name,
+ final Boolean value) {
+ if (value != null) {
+ jsonObject.add(name, value.booleanValue());
+ }
+ }
+
+ private static void addIfNotNull(final JsonObject jsonObject, final String name,
+ final JsonValue value) {
+ if (value != null) {
+ jsonObject.add(name, value);
+ }
+ }
+
public TimeToLive getDefaultLeaseTtl() {
return defaultLeaseTtl;
}
diff --git a/src/main/java/io/github/jopenlibs/vault/json/Json.java b/src/main/java/io/github/jopenlibs/vault/json/Json.java
index f9a139f9..49b53fe8 100644
--- a/src/main/java/io/github/jopenlibs/vault/json/Json.java
+++ b/src/main/java/io/github/jopenlibs/vault/json/Json.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2015 EclipseSource.
+ * Copyright (c) 2015, 2016 EclipseSource.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@@ -23,7 +23,6 @@
import java.io.IOException;
import java.io.Reader;
-import java.util.Arrays;
/**
@@ -37,8 +36,8 @@
*
*
* To create a JSON data structure to be serialized, use the methods
- * value(), array(), and object(). For example, the
- * following snippet will produce the JSON string {"foo": 23, "bar": true}:
+ * value(), array(), and object(). For example, the following
+ * snippet will produce the JSON string {"foo": 23, "bar": true}:
*
*/
-@SuppressWarnings("PMD")
public final class Json {
- private Json() {
- // not meant to be instantiated
+ private Json() {
+ // not meant to be instantiated
+ }
+
+ /**
+ * Represents the JSON literal null.
+ */
+ public static final JsonValue NULL = new JsonLiteral("null");
+
+ /**
+ * Represents the JSON literal true.
+ */
+ public static final JsonValue TRUE = new JsonLiteral("true");
+
+ /**
+ * Represents the JSON literal false.
+ */
+ public static final JsonValue FALSE = new JsonLiteral("false");
+
+ /**
+ * Returns a JsonValue instance that represents the given int value.
+ *
+ * @param value
+ * the value to get a JSON representation for
+ * @return a JSON value that represents the given value
+ */
+ public static JsonValue value(int value) {
+ return new JsonNumber(Integer.toString(value, 10));
+ }
+
+ /**
+ * Returns a JsonValue instance that represents the given long value.
+ *
+ * @param value
+ * the value to get a JSON representation for
+ * @return a JSON value that represents the given value
+ */
+ public static JsonValue value(long value) {
+ return new JsonNumber(Long.toString(value, 10));
+ }
+
+ /**
+ * Returns a JsonValue instance that represents the given float value.
+ *
+ * @param value
+ * the value to get a JSON representation for
+ * @return a JSON value that represents the given value
+ */
+ public static JsonValue value(float value) {
+ if (Float.isInfinite(value) || Float.isNaN(value)) {
+ throw new IllegalArgumentException("Infinite and NaN values not permitted in JSON");
}
+ return new JsonNumber(cutOffPointZero(Float.toString(value)));
+ }
- /**
- * Represents the JSON literal null.
- */
- public static final JsonValue NULL = new JsonLiteral("null");
-
- /**
- * Represents the JSON literal true.
- */
- public static final JsonValue TRUE = new JsonLiteral("true");
-
- /**
- * Represents the JSON literal false.
- */
- public static final JsonValue FALSE = new JsonLiteral("false");
-
- /**
- * Returns a JsonValue instance that represents the given int value.
- *
- * @param value the value to get a JSON representation for
- * @return a JSON value that represents the given value
- */
- public static JsonValue value(int value) {
- return new JsonNumber(Integer.toString(value, 10));
+ /**
+ * Returns a JsonValue instance that represents the given double value.
+ *
+ * @param value
+ * the value to get a JSON representation for
+ * @return a JSON value that represents the given value
+ */
+ public static JsonValue value(double value) {
+ if (Double.isInfinite(value) || Double.isNaN(value)) {
+ throw new IllegalArgumentException("Infinite and NaN values not permitted in JSON");
}
+ return new JsonNumber(cutOffPointZero(Double.toString(value)));
+ }
+
+ /**
+ * Returns a JsonValue instance that represents the given string.
+ *
+ * @param string
+ * the string to get a JSON representation for
+ * @return a JSON value that represents the given string
+ */
+ public static JsonValue value(String string) {
+ return string == null ? NULL : new JsonString(string);
+ }
- /**
- * Returns a JsonValue instance that represents the given int[] value.
- *
- * @param value the value to get a JSON representation for
- * @return a JSON value that represents the given value
- */
- public static JsonValue value(int[] value) {
- return new JsonNumber(Arrays.toString(value));
+ /**
+ * Returns a JsonValue instance that represents the given boolean value.
+ *
+ * @param value
+ * the value to get a JSON representation for
+ * @return a JSON value that represents the given value
+ */
+ public static JsonValue value(boolean value) {
+ return value ? TRUE : FALSE;
+ }
+
+ /**
+ * Creates a new empty JsonArray. This is equivalent to creating a new JsonArray using the
+ * constructor.
+ *
+ * @return a new empty JSON array
+ */
+ public static JsonArray array() {
+ return new JsonArray();
+ }
+
+ /**
+ * Creates a new JsonArray that contains the JSON representations of the given int
+ * values.
+ *
+ * @param values
+ * the values to be included in the new JSON array
+ * @return a new JSON array that contains the given values
+ */
+ public static JsonArray array(int... values) {
+ if (values == null) {
+ throw new NullPointerException("values is null");
+ }
+ JsonArray array = new JsonArray();
+ for (int value : values) {
+ array.add(value);
}
+ return array;
+ }
- /**
- * Returns a JsonValue instance that represents the given long value.
- *
- * @param value the value to get a JSON representation for
- * @return a JSON value that represents the given value
- */
- public static JsonValue value(long value) {
- return new JsonNumber(Long.toString(value, 10));
+ /**
+ * Creates a new JsonArray that contains the JSON representations of the given long
+ * values.
+ *
+ * @param values
+ * the values to be included in the new JSON array
+ * @return a new JSON array that contains the given values
+ */
+ public static JsonArray array(long... values) {
+ if (values == null) {
+ throw new NullPointerException("values is null");
}
+ JsonArray array = new JsonArray();
+ for (long value : values) {
+ array.add(value);
+ }
+ return array;
+ }
- /**
- * Returns a JsonValue instance that represents the given float value.
- *
- * @param value the value to get a JSON representation for
- * @return a JSON value that represents the given value
- */
- public static JsonValue value(float value) {
- if (Float.isInfinite(value) || Float.isNaN(value)) {
- throw new IllegalArgumentException("Infinite and NaN values not permitted in JSON");
- }
- return new JsonNumber(cutOffPointZero(Float.toString(value)));
+ /**
+ * Creates a new JsonArray that contains the JSON representations of the given float
+ * values.
+ *
+ * @param values
+ * the values to be included in the new JSON array
+ * @return a new JSON array that contains the given values
+ */
+ public static JsonArray array(float... values) {
+ if (values == null) {
+ throw new NullPointerException("values is null");
+ }
+ JsonArray array = new JsonArray();
+ for (float value : values) {
+ array.add(value);
}
+ return array;
+ }
- /**
- * Returns a JsonValue instance that represents the given double value.
- *
- * @param value the value to get a JSON representation for
- * @return a JSON value that represents the given value
- */
- public static JsonValue value(double value) {
- if (Double.isInfinite(value) || Double.isNaN(value)) {
- throw new IllegalArgumentException("Infinite and NaN values not permitted in JSON");
- }
- return new JsonNumber(cutOffPointZero(Double.toString(value)));
+ /**
+ * Creates a new JsonArray that contains the JSON representations of the given double
+ * values.
+ *
+ * @param values
+ * the values to be included in the new JSON array
+ * @return a new JSON array that contains the given values
+ */
+ public static JsonArray array(double... values) {
+ if (values == null) {
+ throw new NullPointerException("values is null");
+ }
+ JsonArray array = new JsonArray();
+ for (double value : values) {
+ array.add(value);
}
+ return array;
+ }
- /**
- * Returns a JsonValue instance that represents the given string.
- *
- * @param string the string to get a JSON representation for
- * @return a JSON value that represents the given string
- */
- public static JsonValue value(String string) {
- return string == null ? NULL : new JsonString(string);
+ /**
+ * Creates a new JsonArray that contains the JSON representations of the given
+ * boolean values.
+ *
+ * @param values
+ * the values to be included in the new JSON array
+ * @return a new JSON array that contains the given values
+ */
+ public static JsonArray array(boolean... values) {
+ if (values == null) {
+ throw new NullPointerException("values is null");
}
+ JsonArray array = new JsonArray();
+ for (boolean value : values) {
+ array.add(value);
+ }
+ return array;
+ }
+
+ /**
+ * Creates a new JsonArray that contains the JSON representations of the given strings.
+ *
+ * @param strings
+ * the strings to be included in the new JSON array
+ * @return a new JSON array that contains the given strings
+ */
+ public static JsonArray array(String... strings) {
+ if (strings == null) {
+ throw new NullPointerException("values is null");
+ }
+ JsonArray array = new JsonArray();
+ for (String value : strings) {
+ array.add(value);
+ }
+ return array;
+ }
+
+ /**
+ * Creates a new empty JsonObject. This is equivalent to creating a new JsonObject using the
+ * constructor.
+ *
+ * @return a new empty JSON object
+ */
+ public static JsonObject object() {
+ return new JsonObject();
+ }
+
+ /**
+ * Parses the given input string as JSON. The input must contain a valid JSON value, optionally
+ * padded with whitespace.
+ *
+ * @param string
+ * the input string, must be valid JSON
+ * @return a value that represents the parsed JSON
+ * @throws ParseException
+ * if the input is not valid JSON
+ */
+ public static JsonValue parse(String string) {
+ if (string == null) {
+ throw new NullPointerException("string is null");
+ }
+ DefaultHandler handler = new DefaultHandler();
+ new JsonParser(handler).parse(string);
+ return handler.getValue();
+ }
+
+ /**
+ * Reads the entire input from the given reader and parses it as JSON. The input must contain a
+ * valid JSON value, optionally padded with whitespace.
+ *
+ * Characters are read in chunks into an input buffer. Hence, wrapping a reader in an additional
+ * BufferedReader likely won't improve reading performance.
+ *
+ *
+ * @param reader
+ * the reader to read the JSON value from
+ * @return a value that represents the parsed JSON
+ * @throws IOException
+ * if an I/O error occurs in the reader
+ * @throws ParseException
+ * if the input is not valid JSON
+ */
+ public static JsonValue parse(Reader reader) throws IOException {
+ if (reader == null) {
+ throw new NullPointerException("reader is null");
+ }
+ DefaultHandler handler = new DefaultHandler();
+ new JsonParser(handler).parse(reader);
+ return handler.getValue();
+ }
- /**
- * Returns a JsonValue instance that represents the given boolean value.
- *
- * @param value the value to get a JSON representation for
- * @return a JSON value that represents the given value
- */
- public static JsonValue value(boolean value) {
- return value ? TRUE : FALSE;
+ private static String cutOffPointZero(String string) {
+ if (string.endsWith(".0")) {
+ return string.substring(0, string.length() - 2);
}
+ return string;
+ }
- /**
- * Creates a new empty JsonArray. This is equivalent to creating a new JsonArray using the
- * constructor.
- *
- * @return a new empty JSON array
- */
- public static JsonValue array() {
- return new JsonArray();
+ static class DefaultHandler extends JsonHandler {
+
+ protected JsonValue value;
+
+ @Override
+ public JsonArray startArray() {
+ return new JsonArray();
}
- /**
- * Creates a new JsonArray that contains the JSON representations of the given int
- * values.
- *
- * @param values the values to be included in the new JSON array
- * @return a new JSON array that contains the given values
- */
- public static JsonArray array(int... values) {
- if (values == null) {
- throw new NullPointerException("values is null");
- }
- JsonArray array = new JsonArray();
- for (int value : values) {
- array.add(value);
- }
- return array;
+ @Override
+ public JsonObject startObject() {
+ return new JsonObject();
}
- /**
- * Creates a new JsonArray that contains the JSON representations of the given long
- * values.
- *
- * @param values the values to be included in the new JSON array
- * @return a new JSON array that contains the given values
- */
- public static JsonArray array(long... values) {
- if (values == null) {
- throw new NullPointerException("values is null");
- }
- JsonArray array = new JsonArray();
- for (long value : values) {
- array.add(value);
- }
- return array;
+ @Override
+ public void endNull() {
+ value = NULL;
}
- /**
- * Creates a new JsonArray that contains the JSON representations of the given
- * float values.
- *
- * @param values the values to be included in the new JSON array
- * @return a new JSON array that contains the given values
- */
- public static JsonArray array(float... values) {
- if (values == null) {
- throw new NullPointerException("values is null");
- }
- JsonArray array = new JsonArray();
- for (float value : values) {
- array.add(value);
- }
- return array;
+ @Override
+ public void endBoolean(boolean bool) {
+ value = bool ? TRUE : FALSE;
}
- /**
- * Creates a new JsonArray that contains the JSON representations of the given
- * double values.
- *
- * @param values the values to be included in the new JSON array
- * @return a new JSON array that contains the given values
- */
- public static JsonArray array(double... values) {
- if (values == null) {
- throw new NullPointerException("values is null");
- }
- JsonArray array = new JsonArray();
- for (double value : values) {
- array.add(value);
- }
- return array;
+ @Override
+ public void endString(String string) {
+ value = new JsonString(string);
}
- /**
- * Creates a new JsonArray that contains the JSON representations of the given
- * boolean values.
- *
- * @param values the values to be included in the new JSON array
- * @return a new JSON array that contains the given values
- */
- public static JsonArray array(boolean... values) {
- if (values == null) {
- throw new NullPointerException("values is null");
- }
- JsonArray array = new JsonArray();
- for (boolean value : values) {
- array.add(value);
- }
- return array;
+ @Override
+ public void endNumber(String string) {
+ value = new JsonNumber(string);
}
- /**
- * Creates a new JsonArray that contains the JSON representations of the given strings.
- *
- * @param strings the strings to be included in the new JSON array
- * @return a new JSON array that contains the given strings
- */
- public static JsonArray array(String... strings) {
- if (strings == null) {
- throw new NullPointerException("values is null");
- }
- JsonArray array = new JsonArray();
- for (String value : strings) {
- array.add(value);
- }
- return array;
+ @Override
+ public void endArray(JsonArray array) {
+ value = array;
}
- /**
- * Creates a new empty JsonObject. This is equivalent to creating a new JsonObject using the
- * constructor.
- *
- * @return a new empty JSON object
- */
- public static JsonObject object() {
- return new JsonObject();
+ @Override
+ public void endObject(JsonObject object) {
+ value = object;
}
- /**
- * Parses the given input string as JSON. The input must contain a valid JSON value, optionally
- * padded with whitespace.
- *
- * @param string the input string, must be valid JSON
- * @return a value that represents the parsed JSON
- * @throws ParseException if the input is not valid JSON
- */
- public static JsonValue parse(String string) {
- if (string == null) {
- throw new NullPointerException("string is null");
- }
- try {
- return new JsonParser(string).parse();
- } catch (IOException exception) {
- // JsonParser does not throw IOException for String
- throw new RuntimeException(exception);
- }
+ @Override
+ public void endArrayValue(JsonArray array) {
+ array.add(value);
}
- /**
- * Reads the entire input stream from the given reader and parses it as JSON. The input must
- * contain a valid JSON value, optionally padded with whitespace.
- *
- * Characters are read in chunks and buffered internally, therefore wrapping an existing reader
- * in an additional BufferedReader does not improve reading
- * performance.
- *
- *
- * @param reader the reader to read the JSON value from
- * @return a value that represents the parsed JSON
- * @throws IOException if an I/O error occurs in the reader
- * @throws ParseException if the input is not valid JSON
- */
- public static JsonValue parse(Reader reader) throws IOException {
- if (reader == null) {
- throw new NullPointerException("reader is null");
- }
- return new JsonParser(reader).parse();
+ @Override
+ public void endObjectValue(JsonObject object, String name) {
+ object.add(name, value);
}
- private static String cutOffPointZero(String string) {
- if (string.endsWith(".0")) {
- return string.substring(0, string.length() - 2);
- }
- return string;
+ JsonValue getValue() {
+ return value;
}
+ }
+
}
diff --git a/src/main/java/io/github/jopenlibs/vault/json/JsonArray.java b/src/main/java/io/github/jopenlibs/vault/json/JsonArray.java
index 1f3e13a3..fc812025 100644
--- a/src/main/java/io/github/jopenlibs/vault/json/JsonArray.java
+++ b/src/main/java/io/github/jopenlibs/vault/json/JsonArray.java
@@ -22,6 +22,7 @@
package io.github.jopenlibs.vault.json;
import java.io.IOException;
+import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@@ -58,376 +59,453 @@
* This class is not supposed to be extended by clients.
*
*/
-@SuppressWarnings({"serial", "PMD"}) // use default serial UID
+@SuppressWarnings("serial") // use default serial UID
public class JsonArray extends JsonValue implements Iterable {
- private final List values;
-
- /**
- * Creates a new empty JsonArray.
- */
- public JsonArray() {
- values = new ArrayList<>();
- }
-
- /**
- * Creates a new JsonArray with the contents of the specified JSON array.
- *
- * @param array the JsonArray to get the initial contents from, must not be null
- */
- public JsonArray(JsonArray array) {
- this(array, false);
- }
-
- private JsonArray(JsonArray array, boolean unmodifiable) {
- if (array == null) {
- throw new NullPointerException("array is null");
- }
- if (unmodifiable) {
- values = Collections.unmodifiableList(array.values);
- } else {
- values = new ArrayList<>(array.values);
- }
- }
-
- /**
- * Returns an unmodifiable wrapper for the specified JsonArray. This method allows to provide
- * read-only access to a JsonArray.
- *
- * The returned JsonArray is backed by the given array and reflects subsequent changes. Attempts
- * to modify the returned JsonArray result in an UnsupportedOperationException.
- *
- *
- * @param array the JsonArray for which an unmodifiable JsonArray is to be returned
- * @return an unmodifiable view of the specified JsonArray
- */
- public static JsonArray unmodifiableArray(JsonArray array) {
- return new JsonArray(array, true);
- }
-
- /**
- * Appends the JSON representation of the specified int value to the end of this
- * array.
- *
- * @param value the value to add to the array
- * @return the array itself, to enable method chaining
- */
- public JsonArray add(int value) {
- values.add(Json.value(value));
- return this;
- }
-
- /**
- * Appends the JSON representation of the specified long value to the end of this
- * array.
- *
- * @param value the value to add to the array
- * @return the array itself, to enable method chaining
- */
- public JsonArray add(long value) {
- values.add(Json.value(value));
- return this;
- }
-
- /**
- * Appends the JSON representation of the specified float value to the end of this
- * array.
- *
- * @param value the value to add to the array
- * @return the array itself, to enable method chaining
- */
- public JsonArray add(float value) {
- values.add(Json.value(value));
- return this;
- }
-
- /**
- * Appends the JSON representation of the specified double value to the end of this
- * array.
- *
- * @param value the value to add to the array
- * @return the array itself, to enable method chaining
- */
- public JsonArray add(double value) {
- values.add(Json.value(value));
- return this;
- }
-
- /**
- * Appends the JSON representation of the specified boolean value to the end of
- * this array.
- *
- * @param value the value to add to the array
- * @return the array itself, to enable method chaining
- */
- public JsonArray add(boolean value) {
- values.add(Json.value(value));
- return this;
- }
-
- /**
- * Appends the JSON representation of the specified string to the end of this array.
- *
- * @param value the string to add to the array
- * @return the array itself, to enable method chaining
- */
- public JsonArray add(String value) {
- values.add(Json.value(value));
- return this;
- }
-
- /**
- * Appends the specified JSON value to the end of this array.
- *
- * @param value the JsonValue to add to the array, must not be null
- * @return the array itself, to enable method chaining
- */
- public JsonArray add(JsonValue value) {
- if (value == null) {
- throw new NullPointerException("value is null");
- }
- values.add(value);
- return this;
- }
-
- /**
- * Replaces the element at the specified position in this array with the JSON representation of
- * the specified int value.
- *
- * @param index the index of the array element to replace
- * @param value the value to be stored at the specified array position
- * @return the array itself, to enable method chaining
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonArray set(int index, int value) {
- values.set(index, Json.value(value));
- return this;
- }
-
- /**
- * Replaces the element at the specified position in this array with the JSON representation of
- * the specified long value.
- *
- * @param index the index of the array element to replace
- * @param value the value to be stored at the specified array position
- * @return the array itself, to enable method chaining
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonArray set(int index, long value) {
- values.set(index, Json.value(value));
- return this;
+ private final List values;
+
+ /**
+ * Creates a new empty JsonArray.
+ */
+ public JsonArray() {
+ values = new ArrayList();
+ }
+
+ /**
+ * Creates a new JsonArray with the contents of the specified JSON array.
+ *
+ * @param array
+ * the JsonArray to get the initial contents from, must not be null
+ */
+ public JsonArray(JsonArray array) {
+ this(array, false);
+ }
+
+ private JsonArray(JsonArray array, boolean unmodifiable) {
+ if (array == null) {
+ throw new NullPointerException("array is null");
}
-
- /**
- * Replaces the element at the specified position in this array with the JSON representation of
- * the specified float value.
- *
- * @param index the index of the array element to replace
- * @param value the value to be stored at the specified array position
- * @return the array itself, to enable method chaining
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonArray set(int index, float value) {
- values.set(index, Json.value(value));
- return this;
+ if (unmodifiable) {
+ values = Collections.unmodifiableList(array.values);
+ } else {
+ values = new ArrayList(array.values);
}
-
- /**
- * Replaces the element at the specified position in this array with the JSON representation of
- * the specified double value.
- *
- * @param index the index of the array element to replace
- * @param value the value to be stored at the specified array position
- * @return the array itself, to enable method chaining
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonArray set(int index, double value) {
- values.set(index, Json.value(value));
- return this;
+ }
+
+ /**
+ * Reads a JSON array from the given reader.
+ *
+ * Characters are read in chunks and buffered internally, therefore wrapping an existing reader in
+ * an additional BufferedReader does not improve reading
+ * performance.
+ *
+ *
+ * @param reader
+ * the reader to read the JSON array from
+ * @return the JSON array that has been read
+ * @throws IOException
+ * if an I/O error occurs in the reader
+ * @throws ParseException
+ * if the input is not valid JSON
+ * @throws UnsupportedOperationException
+ * if the input does not contain a JSON array
+ * @deprecated Use {@link Json#parse(Reader)}{@link JsonValue#asArray() .asArray()} instead
+ */
+ @Deprecated
+ public static JsonArray readFrom(Reader reader) throws IOException {
+ return JsonValue.readFrom(reader).asArray();
+ }
+
+ /**
+ * Reads a JSON array from the given string.
+ *
+ * @param string
+ * the string that contains the JSON array
+ * @return the JSON array that has been read
+ * @throws ParseException
+ * if the input is not valid JSON
+ * @throws UnsupportedOperationException
+ * if the input does not contain a JSON array
+ * @deprecated Use {@link Json#parse(String)}{@link JsonValue#asArray() .asArray()} instead
+ */
+ @Deprecated
+ public static JsonArray readFrom(String string) {
+ return JsonValue.readFrom(string).asArray();
+ }
+
+ /**
+ * Returns an unmodifiable wrapper for the specified JsonArray. This method allows to provide
+ * read-only access to a JsonArray.
+ *
+ * The returned JsonArray is backed by the given array and reflects subsequent changes. Attempts
+ * to modify the returned JsonArray result in an UnsupportedOperationException.
+ *
+ *
+ * @param array
+ * the JsonArray for which an unmodifiable JsonArray is to be returned
+ * @return an unmodifiable view of the specified JsonArray
+ */
+ public static JsonArray unmodifiableArray(JsonArray array) {
+ return new JsonArray(array, true);
+ }
+
+ /**
+ * Appends the JSON representation of the specified int value to the end of this
+ * array.
+ *
+ * @param value
+ * the value to add to the array
+ * @return the array itself, to enable method chaining
+ */
+ public JsonArray add(int value) {
+ values.add(Json.value(value));
+ return this;
+ }
+
+ /**
+ * Appends the JSON representation of the specified long value to the end of this
+ * array.
+ *
+ * @param value
+ * the value to add to the array
+ * @return the array itself, to enable method chaining
+ */
+ public JsonArray add(long value) {
+ values.add(Json.value(value));
+ return this;
+ }
+
+ /**
+ * Appends the JSON representation of the specified float value to the end of this
+ * array.
+ *
+ * @param value
+ * the value to add to the array
+ * @return the array itself, to enable method chaining
+ */
+ public JsonArray add(float value) {
+ values.add(Json.value(value));
+ return this;
+ }
+
+ /**
+ * Appends the JSON representation of the specified double value to the end of this
+ * array.
+ *
+ * @param value
+ * the value to add to the array
+ * @return the array itself, to enable method chaining
+ */
+ public JsonArray add(double value) {
+ values.add(Json.value(value));
+ return this;
+ }
+
+ /**
+ * Appends the JSON representation of the specified boolean value to the end of this
+ * array.
+ *
+ * @param value
+ * the value to add to the array
+ * @return the array itself, to enable method chaining
+ */
+ public JsonArray add(boolean value) {
+ values.add(Json.value(value));
+ return this;
+ }
+
+ /**
+ * Appends the JSON representation of the specified string to the end of this array.
+ *
+ * @param value
+ * the string to add to the array
+ * @return the array itself, to enable method chaining
+ */
+ public JsonArray add(String value) {
+ values.add(Json.value(value));
+ return this;
+ }
+
+ /**
+ * Appends the specified JSON value to the end of this array.
+ *
+ * @param value
+ * the JsonValue to add to the array, must not be null
+ * @return the array itself, to enable method chaining
+ */
+ public JsonArray add(JsonValue value) {
+ if (value == null) {
+ throw new NullPointerException("value is null");
}
-
- /**
- * Replaces the element at the specified position in this array with the JSON representation of
- * the specified boolean value.
- *
- * @param index the index of the array element to replace
- * @param value the value to be stored at the specified array position
- * @return the array itself, to enable method chaining
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonArray set(int index, boolean value) {
- values.set(index, Json.value(value));
- return this;
+ values.add(value);
+ return this;
+ }
+
+ /**
+ * Replaces the element at the specified position in this array with the JSON representation of
+ * the specified int value.
+ *
+ * @param index
+ * the index of the array element to replace
+ * @param value
+ * the value to be stored at the specified array position
+ * @return the array itself, to enable method chaining
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonArray set(int index, int value) {
+ values.set(index, Json.value(value));
+ return this;
+ }
+
+ /**
+ * Replaces the element at the specified position in this array with the JSON representation of
+ * the specified long value.
+ *
+ * @param index
+ * the index of the array element to replace
+ * @param value
+ * the value to be stored at the specified array position
+ * @return the array itself, to enable method chaining
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonArray set(int index, long value) {
+ values.set(index, Json.value(value));
+ return this;
+ }
+
+ /**
+ * Replaces the element at the specified position in this array with the JSON representation of
+ * the specified float value.
+ *
+ * @param index
+ * the index of the array element to replace
+ * @param value
+ * the value to be stored at the specified array position
+ * @return the array itself, to enable method chaining
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonArray set(int index, float value) {
+ values.set(index, Json.value(value));
+ return this;
+ }
+
+ /**
+ * Replaces the element at the specified position in this array with the JSON representation of
+ * the specified double value.
+ *
+ * @param index
+ * the index of the array element to replace
+ * @param value
+ * the value to be stored at the specified array position
+ * @return the array itself, to enable method chaining
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonArray set(int index, double value) {
+ values.set(index, Json.value(value));
+ return this;
+ }
+
+ /**
+ * Replaces the element at the specified position in this array with the JSON representation of
+ * the specified boolean value.
+ *
+ * @param index
+ * the index of the array element to replace
+ * @param value
+ * the value to be stored at the specified array position
+ * @return the array itself, to enable method chaining
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonArray set(int index, boolean value) {
+ values.set(index, Json.value(value));
+ return this;
+ }
+
+ /**
+ * Replaces the element at the specified position in this array with the JSON representation of
+ * the specified string.
+ *
+ * @param index
+ * the index of the array element to replace
+ * @param value
+ * the string to be stored at the specified array position
+ * @return the array itself, to enable method chaining
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonArray set(int index, String value) {
+ values.set(index, Json.value(value));
+ return this;
+ }
+
+ /**
+ * Replaces the element at the specified position in this array with the specified JSON value.
+ *
+ * @param index
+ * the index of the array element to replace
+ * @param value
+ * the value to be stored at the specified array position, must not be null
+ * @return the array itself, to enable method chaining
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonArray set(int index, JsonValue value) {
+ if (value == null) {
+ throw new NullPointerException("value is null");
}
-
- /**
- * Replaces the element at the specified position in this array with the JSON representation of
- * the specified string.
- *
- * @param index the index of the array element to replace
- * @param value the string to be stored at the specified array position
- * @return the array itself, to enable method chaining
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonArray set(int index, String value) {
- values.set(index, Json.value(value));
- return this;
+ values.set(index, value);
+ return this;
+ }
+
+ /**
+ * Removes the element at the specified index from this array.
+ *
+ * @param index
+ * the index of the element to remove
+ * @return the array itself, to enable method chaining
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonArray remove(int index) {
+ values.remove(index);
+ return this;
+ }
+
+ /**
+ * Returns the number of elements in this array.
+ *
+ * @return the number of elements in this array
+ */
+ public int size() {
+ return values.size();
+ }
+
+ /**
+ * Returns true if this array contains no elements.
+ *
+ * @return true if this array contains no elements
+ */
+ public boolean isEmpty() {
+ return values.isEmpty();
+ }
+
+ /**
+ * Returns the value of the element at the specified position in this array.
+ *
+ * @param index
+ * the index of the array element to return
+ * @return the value of the element at the specified position
+ * @throws IndexOutOfBoundsException
+ * if the index is out of range, i.e. index < 0 or
+ * index >= size
+ */
+ public JsonValue get(int index) {
+ return values.get(index);
+ }
+
+ /**
+ * Returns a list of the values in this array in document order. The returned list is backed by
+ * this array and will reflect subsequent changes. It cannot be used to modify this array.
+ * Attempts to modify the returned list will result in an exception.
+ *
+ * @return a list of the values in this array
+ */
+ public List values() {
+ return Collections.unmodifiableList(values);
+ }
+
+ /**
+ * Returns an iterator over the values of this array in document order. The returned iterator
+ * cannot be used to modify this array.
+ *
+ * @return an iterator over the values of this array
+ */
+ public Iterator iterator() {
+ final Iterator iterator = values.iterator();
+ return new Iterator() {
+
+ public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ public JsonValue next() {
+ return iterator.next();
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ @Override
+ void write(JsonWriter writer) throws IOException {
+ writer.writeArrayOpen();
+ Iterator iterator = iterator();
+ if (iterator.hasNext()) {
+ iterator.next().write(writer);
+ while (iterator.hasNext()) {
+ writer.writeArraySeparator();
+ iterator.next().write(writer);
+ }
}
-
- /**
- * Replaces the element at the specified position in this array with the specified JSON value.
- *
- * @param index the index of the array element to replace
- * @param value the value to be stored at the specified array position, must not be
- * null
- * @return the array itself, to enable method chaining
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonArray set(int index, JsonValue value) {
- if (value == null) {
- throw new NullPointerException("value is null");
- }
- values.set(index, value);
- return this;
+ writer.writeArrayClose();
+ }
+
+ @Override
+ public boolean isArray() {
+ return true;
+ }
+
+ @Override
+ public JsonArray asArray() {
+ return this;
+ }
+
+ @Override
+ public int hashCode() {
+ return values.hashCode();
+ }
+
+
+ /**
+ * Indicates whether a given object is "equal to" this JsonArray. An object is considered equal
+ * if it is also a JsonArray and both arrays contain the same list of values.
+ *
+ * If two JsonArrays are equal, they will also produce the same JSON output.
+ *
+ *
+ * @param object
+ * the object to be compared with this JsonArray
+ * @return true if the specified object is equal to this JsonArray, false
+ * otherwise
+ */
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) {
+ return true;
}
-
- /**
- * Removes the element at the specified index from this array.
- *
- * @param index the index of the element to remove
- * @return the array itself, to enable method chaining
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonArray remove(int index) {
- values.remove(index);
- return this;
+ if (object == null) {
+ return false;
}
-
- /**
- * Returns the number of elements in this array.
- *
- * @return the number of elements in this array
- */
- public int size() {
- return values.size();
- }
-
- /**
- * Returns true if this array contains no elements.
- *
- * @return true if this array contains no elements
- */
- public boolean isEmpty() {
- return values.isEmpty();
- }
-
- /**
- * Returns the value of the element at the specified position in this array.
- *
- * @param index the index of the array element to return
- * @return the value of the element at the specified position
- * @throws IndexOutOfBoundsException if the index is out of range, i.e. index <
- * 0 or
- * index >= size
- */
- public JsonValue get(int index) {
- return values.get(index);
- }
-
- /**
- * Returns a list of the values in this array in document order. The returned list is backed by
- * this array and will reflect subsequent changes. It cannot be used to modify this array.
- * Attempts to modify the returned list will result in an exception.
- *
- * @return a list of the values in this array
- */
- public List values() {
- return Collections.unmodifiableList(values);
- }
-
- /**
- * Returns an iterator over the values of this array in document order. The returned iterator
- * cannot be used to modify this array.
- *
- * @return an iterator over the values of this array
- */
- public Iterator iterator() {
- final Iterator iterator = values.iterator();
- return new Iterator() {
-
- public boolean hasNext() {
- return iterator.hasNext();
- }
-
- public JsonValue next() {
- return iterator.next();
- }
-
- public void remove() {
- throw new UnsupportedOperationException();
- }
- };
- }
-
- @Override
- void write(JsonWriter writer) throws IOException {
- writer.writeArrayOpen();
- Iterator iterator = iterator();
- boolean first = true;
- while (iterator.hasNext()) {
- if (!first) {
- writer.writeArraySeparator();
- }
- iterator.next().write(writer);
- first = false;
- }
- writer.writeArrayClose();
- }
-
- @Override
- public boolean isArray() {
- return true;
- }
-
- @Override
- public JsonArray asArray() {
- return this;
- }
-
- @Override
- public int hashCode() {
- return values.hashCode();
- }
-
- @Override
- public boolean equals(Object object) {
- if (this == object) {
- return true;
- }
- if (object == null) {
- return false;
- }
- if (getClass() != object.getClass()) {
- return false;
- }
- JsonArray other = (JsonArray) object;
- return values.equals(other.values);
+ if (getClass() != object.getClass()) {
+ return false;
}
+ JsonArray other = (JsonArray)object;
+ return values.equals(other.values);
+ }
}
diff --git a/src/main/java/io/github/jopenlibs/vault/json/JsonHandler.java b/src/main/java/io/github/jopenlibs/vault/json/JsonHandler.java
new file mode 100644
index 00000000..8b73cafe
--- /dev/null
+++ b/src/main/java/io/github/jopenlibs/vault/json/JsonHandler.java
@@ -0,0 +1,263 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EclipseSource.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ ******************************************************************************/
+package io.github.jopenlibs.vault.json;
+
+
+/**
+ * A handler for parser events. Instances of this class can be given to a {@link JsonParser}. The
+ * parser will then call the methods of the given handler while reading the input.
+ *
+ * The default implementations of these methods do nothing. Subclasses may override only those
+ * methods they are interested in. They can use getLocation() to access the current
+ * character position of the parser at any point. The start* methods will be called
+ * while the location points to the first character of the parsed element. The end*
+ * methods will be called while the location points to the character position that directly follows
+ * the last character of the parsed element. Example:
+ *
+ * Subclasses that build an object representation of the parsed JSON can return arbitrary handler
+ * objects for JSON arrays and JSON objects in {@link #startArray()} and {@link #startObject()}.
+ * These handler objects will then be provided in all subsequent parser events for this particular
+ * array or object. They can be used to keep track the elements of a JSON array or object.
+ *